├── src ├── arch │ ├── CMakeLists.txt │ ├── arch.h │ └── x86_64.cpp ├── triggers.h ├── printf.h ├── map.h ├── struct.h ├── fake_map.h ├── fake_map.cpp ├── imap.h ├── ast │ ├── CMakeLists.txt │ ├── printer.h │ ├── irbuilderbpf.h │ ├── semantic_analyser.h │ ├── codegen_llvm.h │ ├── ast.cpp │ ├── printer.cpp │ ├── ast.h │ ├── irbuilderbpf.cpp │ └── semantic_analyser.cpp ├── mapkey.h ├── driver.h ├── CMakeLists.txt ├── attached_probe.h ├── types.h ├── driver.cpp ├── types.cpp ├── printf_format_types.h ├── mapkey.cpp ├── printf.cpp ├── bpftrace.h ├── map.cpp ├── bpforc.h ├── main.cpp ├── lexer.l ├── parser.yy ├── attached_probe.cpp └── bpftrace.cpp ├── docker ├── build.sh ├── Dockerfile.ubuntu └── Dockerfile.alpine ├── tests ├── main.cpp ├── README.md ├── ast.cpp ├── CMakeLists.txt ├── parser.cpp ├── semantic_analyser.cpp └── bpftrace.cpp ├── .travis.yml ├── tools ├── biosnoop.bt ├── execsnoop.bt ├── execsnoop_example.txt └── biosnoop_example.txt ├── cmake └── FindLibElf.cmake ├── CMakeLists.txt ├── INSTALL.md ├── README.md └── LICENSE /src/arch/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(arch x86_64.cpp) 2 | -------------------------------------------------------------------------------- /src/triggers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" 4 | { 5 | void BEGIN_trigger() { } 6 | void END_trigger() { } 7 | } 8 | -------------------------------------------------------------------------------- /docker/build.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | mkdir -p "$1" 3 | cd "$1" 4 | cmake -DCMAKE_BUILD_TYPE="$2" -DSTATIC_LINKING:BOOL=ON ../ 5 | shift 2 6 | make "$@" 7 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | int main(int argc, char *argv[]) 4 | { 5 | ::testing::InitGoogleTest(&argc, argv); 6 | return RUN_ALL_TESTS(); 7 | } 8 | -------------------------------------------------------------------------------- /src/printf.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ast.h" 4 | #include "types.h" 5 | 6 | namespace bpftrace { 7 | 8 | std::string verify_format_string(const std::string &fmt, std::vector args); 9 | 10 | } // namespace bpftrace 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | language: 4 | - cpp 5 | 6 | services: 7 | - docker 8 | 9 | script: 10 | - ./build-docker-image.sh 11 | - ./build-debug.sh 12 | - ./build-release.sh 13 | - build-debug/tests/bpftrace_test 14 | - build-release/tests/bpftrace_test 15 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # BPFtrace Tests 2 | 3 | Tests can be run with the `bpftrace_test` executable. 4 | 5 | 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: 6 | 7 | `bpftrace_test --gtest_filter=-codegen*` 8 | -------------------------------------------------------------------------------- /src/map.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imap.h" 4 | 5 | namespace bpftrace { 6 | 7 | class Map : public IMap { 8 | public: 9 | Map(const std::string &name, const SizedType &type, const MapKey &key); 10 | Map(enum bpf_map_type map_type); 11 | virtual ~Map() override; 12 | }; 13 | 14 | } // namespace bpftrace 15 | -------------------------------------------------------------------------------- /docker/Dockerfile.ubuntu: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | RUN apt-get update && apt-get install -y \ 3 | bison \ 4 | cmake \ 5 | flex \ 6 | g++ \ 7 | git \ 8 | libclang-5.0-dev \ 9 | libelf-dev \ 10 | llvm-5.0-dev \ 11 | zlib1g-dev 12 | 13 | COPY build.sh /build.sh 14 | ENTRYPOINT ["bash", "/build.sh"] 15 | -------------------------------------------------------------------------------- /src/arch/arch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace bpftrace { 6 | namespace arch { 7 | 8 | int offset(std::string reg_name); 9 | int max_arg(); 10 | int arg_offset(int arg_num); 11 | int ret_offset(); 12 | int pc_offset(); 13 | std::string name(); 14 | 15 | } // namespace arch 16 | } // namespace bpftrace 17 | -------------------------------------------------------------------------------- /src/struct.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "types.h" 5 | 6 | namespace bpftrace { 7 | 8 | class Field { 9 | public: 10 | SizedType type; 11 | int offset; 12 | }; 13 | 14 | class Struct 15 | { 16 | public: 17 | int size; 18 | std::map fields; 19 | }; 20 | 21 | } // namespace bpftrace 22 | -------------------------------------------------------------------------------- /src/fake_map.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imap.h" 4 | 5 | namespace bpftrace { 6 | 7 | class FakeMap : public IMap { 8 | public: 9 | FakeMap(const std::string &name, const SizedType &type, const MapKey &key); 10 | FakeMap(enum bpf_map_type map_type); 11 | 12 | static int next_mapfd_; 13 | }; 14 | 15 | } // namespace bpftrace 16 | -------------------------------------------------------------------------------- /src/fake_map.cpp: -------------------------------------------------------------------------------- 1 | #include "fake_map.h" 2 | 3 | namespace bpftrace { 4 | 5 | int FakeMap::next_mapfd_ = 1; 6 | 7 | FakeMap::FakeMap(const std::string &name, const SizedType &type, const MapKey &key) 8 | { 9 | mapfd_ = next_mapfd_++; 10 | } 11 | 12 | FakeMap::FakeMap(enum bpf_map_type map_type) 13 | { 14 | mapfd_ = next_mapfd_++; 15 | } 16 | 17 | 18 | } // namespace bpftrace 19 | -------------------------------------------------------------------------------- /src/imap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "mapkey.h" 6 | #include "types.h" 7 | 8 | #include "libbpf.h" 9 | 10 | namespace bpftrace { 11 | 12 | class IMap { 13 | public: 14 | virtual ~IMap() { } 15 | IMap() { } 16 | IMap(const IMap &) = delete; 17 | IMap& operator=(const IMap &) = delete; 18 | 19 | int mapfd_; 20 | std::string name_; 21 | SizedType type_; 22 | MapKey key_; 23 | }; 24 | 25 | } // namespace bpftrace 26 | -------------------------------------------------------------------------------- /src/ast/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(ast 2 | ast.cpp 3 | codegen_llvm.cpp 4 | irbuilderbpf.cpp 5 | printer.cpp 6 | semantic_analyser.cpp 7 | ) 8 | 9 | target_include_directories(ast PUBLIC ${CMAKE_SOURCE_DIR}/src) 10 | target_include_directories(ast PUBLIC ${CMAKE_SOURCE_DIR}/src/ast) 11 | target_include_directories(ast PUBLIC ${CMAKE_BINARY_DIR}) 12 | target_link_libraries(ast arch) 13 | 14 | add_dependencies(ast bcc-build) 15 | ExternalProject_Get_Property(bcc source_dir) 16 | target_include_directories(ast PUBLIC ${source_dir}/src/cc) 17 | -------------------------------------------------------------------------------- /docker/Dockerfile.alpine: -------------------------------------------------------------------------------- 1 | FROM alpine:3.8 2 | RUN apk add --update \ 3 | bison \ 4 | build-base \ 5 | clang-dev \ 6 | clang-static \ 7 | cmake \ 8 | elfutils-dev \ 9 | flex-dev \ 10 | git \ 11 | linux-headers \ 12 | llvm5-dev \ 13 | llvm5-static \ 14 | zlib-dev 15 | 16 | # Put LLVM directories where CMake expects them to be 17 | RUN ln -s /usr/lib/cmake/llvm5 /usr/lib/cmake/llvm 18 | RUN ln -s /usr/include/llvm5/llvm /usr/include/llvm 19 | RUN ln -s /usr/include/llvm5/llvm-c /usr/include/llvm-c 20 | 21 | COPY build.sh /build.sh 22 | ENTRYPOINT ["/bin/sh", "/build.sh"] 23 | -------------------------------------------------------------------------------- /src/mapkey.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "types.h" 7 | 8 | namespace bpftrace { 9 | 10 | class BPFtrace; 11 | 12 | class MapKey 13 | { 14 | public: 15 | std::vector args_; 16 | 17 | bool operator!=(const MapKey &k) const; 18 | 19 | size_t size() const; 20 | std::string argument_type_list() const; 21 | std::string argument_value_list(BPFtrace &bpftrace, 22 | const std::vector &data) const; 23 | 24 | private: 25 | static std::string argument_value(BPFtrace &bpftrace, 26 | const SizedType &arg, 27 | const void *data); 28 | }; 29 | 30 | } // namespace bpftrace 31 | -------------------------------------------------------------------------------- /src/driver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "ast.h" 6 | #include "parser.tab.hh" 7 | 8 | typedef void* yyscan_t; 9 | #define YY_DECL bpftrace::Parser::symbol_type yylex(bpftrace::Driver &driver, yyscan_t yyscanner) 10 | YY_DECL; 11 | 12 | namespace bpftrace { 13 | 14 | class Driver 15 | { 16 | public: 17 | Driver(); 18 | ~Driver(); 19 | 20 | int parse_stdin(); 21 | int parse_str(const std::string &script); 22 | int parse_file(const std::string &f); 23 | void error(const location &l, const std::string &m); 24 | void error(const std::string &m); 25 | 26 | ast::Program *root_; 27 | 28 | private: 29 | std::unique_ptr parser_; 30 | yyscan_t scanner_; 31 | }; 32 | 33 | } // namespace bpftrace 34 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (STATIC_LINKING) 2 | set(CMAKE_EXE_LINKER_FLAGS "-static") 3 | endif() 4 | 5 | add_executable(bpftrace 6 | attached_probe.cpp 7 | bpftrace.cpp 8 | driver.cpp 9 | fake_map.cpp 10 | main.cpp 11 | map.cpp 12 | mapkey.cpp 13 | printf.cpp 14 | types.cpp 15 | ) 16 | 17 | target_link_libraries(bpftrace arch ast parser) 18 | 19 | llvm_map_components_to_libnames(llvm_libs bpfcodegen ipo irreader mcjit orcjit) 20 | target_link_libraries(bpftrace ${llvm_libs}) 21 | 22 | add_dependencies(bpftrace bcc-build) 23 | ExternalProject_Get_Property(bcc source_dir binary_dir) 24 | target_include_directories(bpftrace PUBLIC ${source_dir}/src/cc) 25 | target_link_libraries(bpftrace ${binary_dir}/src/cc/libbpf.a) 26 | target_link_libraries(bpftrace ${binary_dir}/src/cc/libbcc-loader-static.a) 27 | target_link_libraries(bpftrace ${binary_dir}/src/cc/libbcc.a) 28 | target_link_libraries(bpftrace ${LIBELF_LIBRARIES}) 29 | -------------------------------------------------------------------------------- /src/attached_probe.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | #include "libbpf.h" 6 | 7 | namespace bpftrace { 8 | 9 | bpf_probe_attach_type attachtype(ProbeType t); 10 | bpf_prog_type progtype(ProbeType t); 11 | 12 | class AttachedProbe 13 | { 14 | public: 15 | AttachedProbe(Probe &probe, std::tuple func); 16 | ~AttachedProbe(); 17 | AttachedProbe(const AttachedProbe &) = delete; 18 | AttachedProbe& operator=(const AttachedProbe &) = delete; 19 | 20 | private: 21 | std::string eventprefix() const; 22 | std::string eventname() const; 23 | static std::string sanitise(const std::string &str); 24 | uint64_t offset() const; 25 | void load_prog(); 26 | void attach_kprobe(); 27 | void attach_uprobe(); 28 | void attach_tracepoint(); 29 | void attach_profile(); 30 | 31 | Probe &probe_; 32 | std::tuple func_; 33 | std::vector perf_event_fds_; 34 | int progfd_; 35 | }; 36 | 37 | } // namespace bpftrace 38 | -------------------------------------------------------------------------------- /tools/biosnoop.bt: -------------------------------------------------------------------------------- 1 | // 2 | // biosnoop.bt - basic block I/O tracing tool, showing per I/O latency. 3 | // For Linux, uses bpftrace, eBPF. 4 | // 5 | // TODO: switch to block tracepoints. Add device, offset, and size columns. 6 | // 7 | // 15-Nov-2017 Brendan Gregg Created this. 8 | // 9 | 10 | BEGIN 11 | { 12 | printf("%-12s %-16s %-6s %7s\n", "TIME(ms)", "COMM", "PID", "LAT(ms)"); 13 | @epoch = nsecs; 14 | } 15 | 16 | kprobe:blk_account_io_start 17 | { 18 | @start[arg0] = nsecs; 19 | @iopid[arg0] = pid; 20 | @iocomm[arg0] = comm; 21 | } 22 | 23 | kprobe:blk_account_io_completion 24 | /@start[arg0] != 0 && @iopid[arg0] != 0 && @iocomm[arg0] != ""/ 25 | 26 | { 27 | $now = nsecs; 28 | printf("%-12u %-16s %-6d %7d\n", 29 | ($now - @epoch) / 1000000, @iocomm[arg0], @iopid[arg0], 30 | ($now - @start[arg0]) / 1000000); 31 | 32 | delete(@start[arg0]); 33 | delete(@iopid[arg0]); 34 | delete(@iocomm[arg0]); 35 | } 36 | 37 | END 38 | { 39 | delete(@epoch); 40 | } 41 | -------------------------------------------------------------------------------- /src/ast/printer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "ast.h" 5 | 6 | namespace bpftrace { 7 | namespace ast { 8 | 9 | class Printer : public Visitor { 10 | public: 11 | explicit Printer(std::ostream &out) : out_(out) { } 12 | 13 | void visit(Integer &integer) override; 14 | void visit(String &string) override; 15 | void visit(Builtin &builtin) override; 16 | void visit(Call &call) override; 17 | void visit(Map &map) override; 18 | void visit(Variable &var) override; 19 | void visit(Binop &binop) override; 20 | void visit(Unop &unop) override; 21 | void visit(FieldAccess &acc) override; 22 | void visit(Cast &cast) override; 23 | void visit(ExprStatement &expr) override; 24 | void visit(AssignMapStatement &assignment) override; 25 | void visit(AssignVarStatement &assignment) override; 26 | void visit(Predicate &pred) override; 27 | void visit(AttachPoint &ap) override; 28 | void visit(Probe &probe) override; 29 | void visit(Include &include) override; 30 | void visit(Program &program) override; 31 | 32 | int depth_ = 0; 33 | 34 | private: 35 | std::ostream &out_; 36 | }; 37 | 38 | } // namespace ast 39 | } // namespace bpftrace 40 | -------------------------------------------------------------------------------- /tools/execsnoop.bt: -------------------------------------------------------------------------------- 1 | // 2 | // execsnoop.bt - basic process exec tracing tool. 3 | // For Linux, uses bpftrace, eBPF. 4 | // 5 | // This traces when processes call exec(). It is handy for identifying new 6 | // processes created via the usual fork()->exec() sequence. Note that the 7 | // return value is not currently traced, so the exec() may have failed. 8 | // Also, only the first five arguments are currently printed. 9 | // 10 | // TODO: switch to proc tracepoints. Support more args. Include retval. 11 | // 12 | // 15-Nov-2017 Brendan Gregg Created this. 13 | // 14 | 15 | BEGIN 16 | { 17 | printf("%-10s %-5s %s\n", "TIME(ms)", "PID", "ARGS"); 18 | @epoch = nsecs; 19 | } 20 | 21 | kprobe:sys_execve 22 | { 23 | $step = 8; // sizeof (char *) 24 | $ptr = arg1; 25 | 26 | $now = nsecs; 27 | printf("%-10u %-5d %s", 28 | ($now - @epoch) / 1000000, pid, 29 | str(*$ptr)); 30 | // unrolled loop for now: 31 | $ptr = $ptr + $step; printf(" %s", str(*$ptr)); 32 | $ptr = $ptr + $step; printf(" %s", str(*$ptr)); 33 | $ptr = $ptr + $step; printf(" %s", str(*$ptr)); 34 | $ptr = $ptr + $step; printf(" %s", str(*$ptr)); 35 | $ptr = $ptr + $step; printf(" %s", str(*$ptr)); 36 | printf("\n"); 37 | } 38 | 39 | END 40 | { 41 | delete(@epoch); 42 | } 43 | -------------------------------------------------------------------------------- /src/arch/x86_64.cpp: -------------------------------------------------------------------------------- 1 | #include "arch.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace bpftrace { 7 | namespace arch { 8 | 9 | static std::array registers = { 10 | "r15", 11 | "r14", 12 | "r13", 13 | "r12", 14 | "bp", 15 | "bx", 16 | "r11", 17 | "r10", 18 | "r9", 19 | "r8", 20 | "ax", 21 | "cx", 22 | "dx", 23 | "si", 24 | "di", 25 | "orig_ax", 26 | "ip", 27 | "cs", 28 | "flags", 29 | "sp", 30 | "ss", 31 | "fs_base", 32 | "gs_base", 33 | "ds", 34 | "es", 35 | "fs", 36 | "gs", 37 | }; 38 | 39 | static std::array arg_registers = { 40 | "di", 41 | "si", 42 | "dx", 43 | "cx", 44 | "r8", 45 | "r9", 46 | }; 47 | 48 | int offset(std::string reg_name) 49 | { 50 | auto it = find(registers.begin(), registers.end(), reg_name); 51 | if (it == registers.end()) 52 | return -1; 53 | return distance(registers.begin(), it); 54 | } 55 | 56 | int max_arg() 57 | { 58 | return arg_registers.size() - 1; 59 | } 60 | 61 | int arg_offset(int arg_num) 62 | { 63 | return offset(arg_registers.at(arg_num)); 64 | } 65 | 66 | int ret_offset() 67 | { 68 | return offset("ax"); 69 | } 70 | 71 | int pc_offset() 72 | { 73 | return offset("ip"); 74 | } 75 | 76 | std::string name() 77 | { 78 | return std::string("x86_64"); 79 | } 80 | 81 | } // namespace arch 82 | } // namespace bpftrace 83 | -------------------------------------------------------------------------------- /src/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace bpftrace { 10 | 11 | const int MAX_STACK_SIZE = 32; 12 | const int STRING_SIZE = 64; 13 | 14 | enum class Type 15 | { 16 | none, 17 | integer, 18 | quantize, 19 | count, 20 | stack, 21 | ustack, 22 | string, 23 | sym, 24 | usym, 25 | cast, 26 | }; 27 | 28 | std::ostream &operator<<(std::ostream &os, Type type); 29 | 30 | class SizedType 31 | { 32 | public: 33 | SizedType() : type(Type::none), size(0) { } 34 | SizedType(Type type, size_t size, const std::string &cast_type = "") 35 | : type(type), size(size), cast_type(cast_type) { } 36 | Type type; 37 | size_t size; 38 | std::string cast_type; 39 | 40 | bool operator==(const SizedType &t) const; 41 | }; 42 | 43 | std::ostream &operator<<(std::ostream &os, const SizedType &type); 44 | 45 | enum class ProbeType 46 | { 47 | invalid, 48 | kprobe, 49 | kretprobe, 50 | uprobe, 51 | uretprobe, 52 | tracepoint, 53 | profile, 54 | }; 55 | 56 | std::string typestr(Type t); 57 | ProbeType probetype(const std::string &type); 58 | 59 | class Probe 60 | { 61 | public: 62 | ProbeType type; 63 | std::string path; 64 | std::string attach_point; 65 | std::string prog_name; 66 | std::string name; 67 | int freq; 68 | }; 69 | 70 | } // namespace bpftrace 71 | -------------------------------------------------------------------------------- /src/driver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "driver.h" 4 | 5 | extern FILE *yyin; 6 | extern void *yy_scan_string(const char *yy_str, yyscan_t yyscanner); 7 | extern void yyset_in(FILE *_in_str, yyscan_t yyscanner); 8 | extern int yylex_init(yyscan_t *scanner); 9 | extern int yylex_destroy (yyscan_t yyscanner); 10 | 11 | namespace bpftrace { 12 | 13 | Driver::Driver() 14 | { 15 | yylex_init(&scanner_); 16 | parser_ = std::make_unique(*this, scanner_); 17 | } 18 | 19 | Driver::~Driver() 20 | { 21 | yylex_destroy(scanner_); 22 | } 23 | 24 | int Driver::parse_stdin() 25 | { 26 | return parser_->parse(); 27 | } 28 | 29 | int Driver::parse_str(const std::string &script) 30 | { 31 | yy_scan_string(script.c_str(), scanner_); 32 | int result = parser_->parse(); 33 | return result; 34 | } 35 | 36 | int Driver::parse_file(const std::string &f) 37 | { 38 | FILE *file; 39 | if (!(file = fopen(f.c_str(), "r"))) { 40 | std::cerr << "Error: Could not open file '" << f << "'" << std::endl; 41 | return -1; 42 | } 43 | yyset_in(file, scanner_); 44 | int result = parser_->parse(); 45 | fclose(file); 46 | return result; 47 | } 48 | 49 | void Driver::error(const location &l, const std::string &m) 50 | { 51 | std::cerr << l << ": " << m << std::endl; 52 | } 53 | 54 | void Driver::error(const std::string &m) 55 | { 56 | std::cerr << m << std::endl; 57 | } 58 | 59 | } // namespace bpftrace 60 | -------------------------------------------------------------------------------- /src/ast/irbuilderbpf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ast.h" 4 | #include "bpftrace.h" 5 | #include "types.h" 6 | 7 | #include 8 | 9 | namespace bpftrace { 10 | namespace ast { 11 | 12 | using namespace llvm; 13 | 14 | class IRBuilderBPF : public IRBuilder<> 15 | { 16 | public: 17 | IRBuilderBPF(LLVMContext &context, 18 | Module &module, 19 | BPFtrace &bpftrace); 20 | 21 | AllocaInst *CreateAllocaBPF(llvm::Type *ty, const std::string &name=""); 22 | AllocaInst *CreateAllocaBPF(const SizedType &stype, const std::string &name=""); 23 | AllocaInst *CreateAllocaMapKey(int bytes, const std::string &name=""); 24 | llvm::Type *GetType(const SizedType &stype); 25 | CallInst *CreateBpfPseudoCall(int mapfd); 26 | CallInst *CreateBpfPseudoCall(Map &map); 27 | Value *CreateMapLookupElem(Map &map, AllocaInst *key); 28 | void CreateMapUpdateElem(Map &map, AllocaInst *key, Value *val); 29 | void CreateMapDeleteElem(Map &map, AllocaInst *key); 30 | void CreateProbeRead(AllocaInst *dst, size_t size, Value *src); 31 | void CreateProbeReadStr(AllocaInst *dst, size_t size, Value *src); 32 | CallInst *CreateGetNs(); 33 | CallInst *CreateGetPidTgid(); 34 | CallInst *CreateGetUidGid(); 35 | CallInst *CreateGetCpuId(); 36 | CallInst *CreateGetStackId(Value *ctx, bool ustack); 37 | void CreateGetCurrentComm(AllocaInst *buf, size_t size); 38 | void CreatePerfEventOutput(Value *ctx, Value *data, size_t size); 39 | 40 | private: 41 | Module &module_; 42 | BPFtrace &bpftrace_; 43 | }; 44 | 45 | } // namespace ast 46 | } // namespace bpftrace 47 | -------------------------------------------------------------------------------- /tools/execsnoop_example.txt: -------------------------------------------------------------------------------- 1 | Demonstrations of execsnoop, the Linux BPF/bpftrace version. 2 | 3 | 4 | Tracing all new process execution (via exec()): 5 | 6 | # bpftrace execsnoop.bt 7 | Attaching 3 probes... 8 | TIME(ms) PID ARGS 9 | 2460 3466 ls --color=auto -lh execsnoop.bt execsnoop.bt.0 execsnoop.bt.1 10 | 3996 3467 man ls 11 | 4005 3473 preconv -e UTF-8 12 | 4005 3473 preconv -e UTF-8 13 | 4005 3473 preconv -e UTF-8 14 | 4005 3473 preconv -e UTF-8 15 | 4005 3473 preconv -e UTF-8 16 | 4005 3474 tbl 17 | 4005 3474 tbl 18 | 4005 3474 tbl 19 | 4005 3474 tbl 20 | 4005 3474 tbl 21 | 4005 3476 nroff -mandoc -rLL=193n -rLT=193n -Tutf8 22 | 4005 3476 nroff -mandoc -rLL=193n -rLT=193n -Tutf8 23 | 4005 3476 nroff -mandoc -rLL=193n -rLT=193n -Tutf8 24 | 4005 3476 nroff -mandoc -rLL=193n -rLT=193n -Tutf8 25 | 4005 3476 nroff -mandoc -rLL=193n -rLT=193n -Tutf8 26 | 4006 3479 pager -rLL=193n 27 | 4006 3479 pager -rLL=193n 28 | 4006 3479 pager -rLL=193n 29 | 4006 3479 pager -rLL=193n 30 | 4006 3479 pager -rLL=193n 31 | 4007 3481 locale charmap 32 | 4008 3482 groff -mtty-char -Tutf8 -mandoc -rLL=193n -rLT=193n 33 | 4009 3483 troff -mtty-char -mandoc -rLL=193n -rLT=193n -Tutf8 34 | 35 | The output begins by showing an "ls" command, and then the process execution 36 | to serve "man ls". The same exec arguments appear multiple times: in this case 37 | they are failing as the $PATH variable is walked, until one finally succeeds. 38 | 39 | This tool can be used to discover unwanted short-lived processes that may be 40 | causing performance issues such as latency perturbations. 41 | -------------------------------------------------------------------------------- /tools/biosnoop_example.txt: -------------------------------------------------------------------------------- 1 | Demonstrations of biosnoop, the Linux BPF/bpftrace version. 2 | 3 | 4 | This traces block I/O, and shows the issuing process (at least, the process 5 | that was on-CPU at the time of queue insert) and the latency of the I/O: 6 | 7 | # bpftrace biosnoop.bt 8 | Attaching 4 probes... 9 | TIME(ms) COMM PID LAT(ms) 10 | 611 bash 4179 10 11 | 611 cksum 4179 0 12 | 627 cksum 4179 15 13 | 641 cksum 4179 13 14 | 644 cksum 4179 3 15 | 658 cksum 4179 13 16 | 673 cksum 4179 14 17 | 686 cksum 4179 13 18 | 701 cksum 4179 14 19 | 710 cksum 4179 8 20 | 717 cksum 4179 6 21 | 728 cksum 4179 10 22 | 735 cksum 4179 6 23 | 751 cksum 4179 10 24 | 758 cksum 4179 17 25 | 783 cksum 4179 12 26 | 796 cksum 4179 25 27 | 802 cksum 4179 32 28 | [...] 29 | 30 | This output shows the cksum process was issuing block I/O, which were 31 | completing with around 12 milliseconds of latency. Each block I/O event is 32 | printed out, with a completion time as the first column, measured from 33 | program start. 34 | 35 | 36 | An example of some background flushing: 37 | 38 | # bpftrace biosnoop.bt 39 | Attaching 4 probes... 40 | TIME(ms) COMM PID LAT(ms) 41 | 2966 jbd2/nvme0n1-8 615 0 42 | 2967 jbd2/nvme0n1-8 615 0 43 | [...] 44 | -------------------------------------------------------------------------------- /src/types.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "types.h" 4 | 5 | namespace bpftrace { 6 | 7 | std::ostream &operator<<(std::ostream &os, Type type) 8 | { 9 | os << typestr(type); 10 | return os; 11 | } 12 | 13 | std::ostream &operator<<(std::ostream &os, const SizedType &type) 14 | { 15 | os << type.type; 16 | return os; 17 | } 18 | 19 | bool SizedType::operator==(const SizedType &t) const 20 | { 21 | return type == t.type && size == t.size; 22 | } 23 | 24 | std::string typestr(Type t) 25 | { 26 | switch (t) 27 | { 28 | case Type::none: return "none"; break; 29 | case Type::integer: return "integer"; break; 30 | case Type::quantize: return "quantize"; break; 31 | case Type::count: return "count"; break; 32 | case Type::stack: return "stack"; break; 33 | case Type::ustack: return "ustack"; break; 34 | case Type::string: return "string"; break; 35 | case Type::sym: return "sym"; break; 36 | case Type::usym: return "usym"; break; 37 | case Type::cast: return "cast"; break; 38 | default: abort(); 39 | } 40 | } 41 | 42 | ProbeType probetype(const std::string &type) 43 | { 44 | if (type == "kprobe") 45 | return ProbeType::kprobe; 46 | else if (type == "kretprobe") 47 | return ProbeType::kretprobe; 48 | else if (type == "uprobe") 49 | return ProbeType::uprobe; 50 | else if (type == "uretprobe") 51 | return ProbeType::uretprobe; 52 | else if (type == "BEGIN") 53 | return ProbeType::uprobe; 54 | else if (type == "END") 55 | return ProbeType::uprobe; 56 | else if (type == "tracepoint") 57 | return ProbeType::tracepoint; 58 | else if (type == "profile") 59 | return ProbeType::profile; 60 | abort(); 61 | } 62 | 63 | } // namespace bpftrace 64 | -------------------------------------------------------------------------------- /cmake/FindLibElf.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find libelf 2 | # Once done this will define 3 | # 4 | # LIBELF_FOUND - system has libelf 5 | # LIBELF_INCLUDE_DIRS - the libelf include directory 6 | # LIBELF_LIBRARIES - Link these to use libelf 7 | # LIBELF_DEFINITIONS - Compiler switches required for using libelf 8 | # 9 | # Copyright (c) 2008 Bernhard Walle 10 | # 11 | # Redistribution and use is allowed according to the terms of the New 12 | # BSD license. 13 | # For details see the accompanying COPYING-CMAKE-SCRIPTS file. 14 | # 15 | 16 | 17 | if (LIBELF_LIBRARIES AND LIBELF_INCLUDE_DIRS) 18 | set (LibElf_FIND_QUIETLY TRUE) 19 | endif (LIBELF_LIBRARIES AND LIBELF_INCLUDE_DIRS) 20 | 21 | find_path (LIBELF_INCLUDE_DIRS 22 | NAMES 23 | libelf.h 24 | PATHS 25 | /usr/include 26 | /usr/include/libelf 27 | /usr/local/include 28 | /usr/local/include/libelf 29 | /opt/local/include 30 | /opt/local/include/libelf 31 | /sw/include 32 | /sw/include/libelf 33 | ENV CPATH) 34 | 35 | find_library (LIBELF_LIBRARIES 36 | NAMES 37 | elf 38 | PATHS 39 | /usr/lib 40 | /usr/local/lib 41 | /opt/local/lib 42 | /sw/lib 43 | ENV LIBRARY_PATH 44 | ENV LD_LIBRARY_PATH) 45 | 46 | include (FindPackageHandleStandardArgs) 47 | 48 | 49 | # handle the QUIETLY and REQUIRED arguments and set LIBELF_FOUND to TRUE if all listed variables are TRUE 50 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibElf DEFAULT_MSG 51 | LIBELF_LIBRARIES 52 | LIBELF_INCLUDE_DIRS) 53 | 54 | SET(CMAKE_REQUIRED_LIBRARIES elf) 55 | INCLUDE(CheckCXXSourceCompiles) 56 | CHECK_CXX_SOURCE_COMPILES("#include 57 | int main() { 58 | Elf *e = (Elf*)0; 59 | size_t sz; 60 | elf_getshdrstrndx(e, &sz); 61 | return 0; 62 | }" ELF_GETSHDRSTRNDX) 63 | 64 | mark_as_advanced(LIBELF_INCLUDE_DIRS LIBELF_LIBRARIES ELF_GETSHDRSTRNDX) 65 | -------------------------------------------------------------------------------- /src/printf_format_types.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "types.h" 4 | 5 | namespace bpftrace { 6 | 7 | // valid printf length + specifier combinations 8 | // it's done like this because as of this writing, C++ doesn't have a builtin way to do 9 | // compile time string concatenation. here is the Python 3 code that generates this map: 10 | // #!/usr/bin/python3 11 | // lengths = ("", "hh", "h", "l", "ll", "j", "z", "t") 12 | // specifiers = ("d", "u", "x", "X", "p") 13 | // 14 | // print("{\"s\", Type::string},") 15 | // print(",\n".join([f"{{\"{l+s}\", Type::integer}}" for l in lengths for s in specifiers])) 16 | const std::unordered_map printf_format_types = { 17 | {"s", Type::string}, 18 | {"d", Type::integer}, 19 | {"u", Type::integer}, 20 | {"x", Type::integer}, 21 | {"X", Type::integer}, 22 | {"p", Type::integer}, 23 | {"hhd", Type::integer}, 24 | {"hhu", Type::integer}, 25 | {"hhx", Type::integer}, 26 | {"hhX", Type::integer}, 27 | {"hhp", Type::integer}, 28 | {"hd", Type::integer}, 29 | {"hu", Type::integer}, 30 | {"hx", Type::integer}, 31 | {"hX", Type::integer}, 32 | {"hp", Type::integer}, 33 | {"ld", Type::integer}, 34 | {"lu", Type::integer}, 35 | {"lx", Type::integer}, 36 | {"lX", Type::integer}, 37 | {"lp", Type::integer}, 38 | {"lld", Type::integer}, 39 | {"llu", Type::integer}, 40 | {"llx", Type::integer}, 41 | {"llX", Type::integer}, 42 | {"llp", Type::integer}, 43 | {"jd", Type::integer}, 44 | {"ju", Type::integer}, 45 | {"jx", Type::integer}, 46 | {"jX", Type::integer}, 47 | {"jp", Type::integer}, 48 | {"zd", Type::integer}, 49 | {"zu", Type::integer}, 50 | {"zx", Type::integer}, 51 | {"zX", Type::integer}, 52 | {"zp", Type::integer}, 53 | {"td", Type::integer}, 54 | {"tu", Type::integer}, 55 | {"tx", Type::integer}, 56 | {"tX", Type::integer}, 57 | {"tp", Type::integer} 58 | }; 59 | 60 | } // namespace bpftrace 61 | -------------------------------------------------------------------------------- /src/mapkey.cpp: -------------------------------------------------------------------------------- 1 | #include "bpftrace.h" 2 | #include "mapkey.h" 3 | 4 | namespace bpftrace { 5 | 6 | bool MapKey::operator!=(const MapKey &k) const 7 | { 8 | return args_ != k.args_; 9 | } 10 | 11 | size_t MapKey::size() const 12 | { 13 | size_t size = 0; 14 | for (auto &arg : args_) 15 | size += arg.size; 16 | return size; 17 | } 18 | 19 | std::string MapKey::argument_type_list() const 20 | { 21 | size_t n = args_.size(); 22 | if (n == 0) 23 | return "[]"; 24 | 25 | std::ostringstream list; 26 | list << "["; 27 | for (size_t i = 0; i < n-1; i++) 28 | list << args_.at(i) << ", "; 29 | list << args_.at(n-1) << "]"; 30 | return list.str(); 31 | } 32 | 33 | std::string MapKey::argument_value_list(BPFtrace &bpftrace, 34 | const std::vector &data) const 35 | { 36 | size_t n = args_.size(); 37 | if (n == 0) 38 | return ""; 39 | 40 | std::ostringstream list; 41 | list << "["; 42 | int offset = 0; 43 | for (size_t i = 0; i < n-1; i++) 44 | { 45 | const SizedType &arg = args_.at(i); 46 | list << argument_value(bpftrace, arg, &data.at(offset)) << ", "; 47 | offset += arg.size; 48 | } 49 | const SizedType &arg = args_.at(n-1); 50 | list << argument_value(bpftrace, arg, &data.at(offset)) << "]"; 51 | return list.str(); 52 | } 53 | 54 | std::string MapKey::argument_value(BPFtrace &bpftrace, 55 | const SizedType &arg, 56 | const void *data) 57 | { 58 | switch (arg.type) 59 | { 60 | case Type::integer: 61 | switch (arg.size) 62 | { 63 | case 8: 64 | return std::to_string(*(int64_t*)data); 65 | case 4: 66 | return std::to_string(*(int32_t*)data); 67 | } 68 | case Type::stack: 69 | return bpftrace.get_stack(*(uint32_t*)data, false); 70 | case Type::ustack: 71 | return bpftrace.get_stack(*(uint32_t*)data, true); 72 | case Type::sym: 73 | return bpftrace.resolve_sym(*(uint64_t*)data); 74 | case Type::usym: 75 | return bpftrace.resolve_usym(*(uint64_t*)data); 76 | case Type::string: 77 | return std::string((char*)data); 78 | } 79 | abort(); 80 | } 81 | 82 | } // namespace bpftrace 83 | -------------------------------------------------------------------------------- /tests/ast.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | #include "ast.h" 3 | 4 | namespace bpftrace { 5 | namespace test { 6 | namespace ast { 7 | 8 | using bpftrace::ast::AttachPoint; 9 | using bpftrace::ast::AttachPointList; 10 | using bpftrace::ast::Probe; 11 | 12 | TEST(ast, probe_name_special) 13 | { 14 | AttachPoint ap1("BEGIN"); 15 | AttachPointList attach_points1 = { &ap1 }; 16 | Probe begin(&attach_points1, nullptr, nullptr); 17 | EXPECT_EQ(begin.name(), "BEGIN"); 18 | 19 | AttachPoint ap2("END"); 20 | AttachPointList attach_points2 = { &ap2 }; 21 | Probe end(&attach_points2, nullptr, nullptr); 22 | EXPECT_EQ(end.name(), "END"); 23 | } 24 | 25 | TEST(ast, probe_name_kprobe) 26 | { 27 | AttachPoint ap1("kprobe", "sys_read"); 28 | AttachPointList attach_points1 = { &ap1 }; 29 | Probe kprobe1(&attach_points1, nullptr, nullptr); 30 | EXPECT_EQ(kprobe1.name(), "kprobe:sys_read"); 31 | 32 | AttachPoint ap2("kprobe", "sys_write"); 33 | AttachPointList attach_points2 = { &ap1, &ap2 }; 34 | Probe kprobe2(&attach_points2, nullptr, nullptr); 35 | EXPECT_EQ(kprobe2.name(), "kprobe:sys_read,kprobe:sys_write"); 36 | } 37 | 38 | TEST(ast, probe_name_uprobe) 39 | { 40 | AttachPoint ap1("uprobe", "/bin/sh", "readline"); 41 | AttachPointList attach_points1 = { &ap1 }; 42 | Probe uprobe1(&attach_points1, nullptr, nullptr); 43 | EXPECT_EQ(uprobe1.name(), "uprobe:/bin/sh:readline"); 44 | 45 | AttachPoint ap2("uprobe", "/bin/sh", "somefunc"); 46 | AttachPointList attach_points2 = { &ap1, &ap2 }; 47 | Probe uprobe2(&attach_points2, nullptr, nullptr); 48 | EXPECT_EQ(uprobe2.name(), "uprobe:/bin/sh:readline,uprobe:/bin/sh:somefunc"); 49 | } 50 | 51 | TEST(ast, attach_point_name) 52 | { 53 | AttachPoint ap1("kprobe", "sys_read"); 54 | AttachPoint ap2("kprobe", "sys_thisone"); 55 | AttachPoint ap3("uprobe", "/bin/sh", "readline"); 56 | AttachPointList attach_points = { &ap1, &ap2, &ap3 }; 57 | Probe kprobe(&attach_points, nullptr, nullptr); 58 | EXPECT_EQ(ap2.name("sys_thisone"), "kprobe:sys_thisone"); 59 | 60 | Probe uprobe(&attach_points, nullptr, nullptr); 61 | EXPECT_EQ(ap3.name("readline"), "uprobe:/bin/sh:readline"); 62 | } 63 | 64 | } // namespace ast 65 | } // namespace test 66 | } // namespace bpftrace 67 | -------------------------------------------------------------------------------- /src/ast/semantic_analyser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "ast.h" 7 | #include "bpftrace.h" 8 | #include "map.h" 9 | #include "types.h" 10 | 11 | namespace bpftrace { 12 | namespace ast { 13 | 14 | class SemanticAnalyser : public Visitor { 15 | public: 16 | explicit SemanticAnalyser(Node *root, BPFtrace &bpftrace, std::ostream &out = std::cerr) 17 | : root_(root), 18 | bpftrace_(bpftrace), 19 | out_(out) { } 20 | 21 | void visit(Integer &integer) override; 22 | void visit(String &string) override; 23 | void visit(Builtin &builtin) override; 24 | void visit(Call &call) override; 25 | void visit(Map &map) override; 26 | void visit(Variable &var) override; 27 | void visit(Binop &binop) override; 28 | void visit(Unop &unop) override; 29 | void visit(FieldAccess &acc) override; 30 | void visit(Cast &cast) override; 31 | void visit(ExprStatement &expr) override; 32 | void visit(AssignMapStatement &assignment) override; 33 | void visit(AssignVarStatement &assignment) override; 34 | void visit(Predicate &pred) override; 35 | void visit(AttachPoint &ap) override; 36 | void visit(Probe &probe) override; 37 | void visit(Include &include) override; 38 | void visit(Program &program) override; 39 | 40 | int analyse(); 41 | int create_maps(bool debug=false); 42 | 43 | private: 44 | Node *root_; 45 | BPFtrace &bpftrace_; 46 | std::ostream &out_; 47 | std::ostringstream err_; 48 | int pass_; 49 | const int num_passes_ = 10; 50 | 51 | bool is_final_pass() const; 52 | std::string get_cast_type(Expression *expr); 53 | 54 | bool check_assignment(const Call &call, bool want_map, bool want_var); 55 | bool check_nargs(const Call &call, int expected_nargs); 56 | bool check_varargs(const Call &call, int min_nargs, int max_nargs); 57 | bool check_arg(const Call &call, Type type, int arg_num, bool want_literal=false); 58 | 59 | Probe *probe_; 60 | std::map variable_val_; 61 | std::map map_val_; 62 | std::map map_key_; 63 | bool needs_stackid_map_ = false; 64 | bool has_begin_probe_ = false; 65 | bool has_end_probe_ = false; 66 | }; 67 | 68 | } // namespace ast 69 | } // namespace bpftrace 70 | -------------------------------------------------------------------------------- /src/printf.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "printf.h" 4 | #include "printf_format_types.h" 5 | 6 | namespace bpftrace { 7 | 8 | std::string verify_format_string(const std::string &fmt, std::vector args) 9 | { 10 | std::stringstream message; 11 | const std::regex re("%-?[0-9]*[a-zA-Z]+"); 12 | 13 | auto tokens_begin = std::sregex_iterator(fmt.begin(), fmt.end(), re); 14 | auto tokens_end = std::sregex_iterator(); 15 | 16 | auto num_tokens = std::distance(tokens_begin, tokens_end); 17 | int num_args = args.size(); 18 | if (num_args < num_tokens) 19 | { 20 | message << "printf: Not enough arguments for format string (" << num_args 21 | << " supplied, " << num_tokens << " expected)" << std::endl; 22 | return message.str(); 23 | } 24 | if (num_args > num_tokens) 25 | { 26 | message << "printf: Too many arguments for format string (" << num_args 27 | << " supplied, " << num_tokens << " expected)" << std::endl; 28 | return message.str(); 29 | } 30 | 31 | auto token_iter = tokens_begin; 32 | for (int i=0; istr()[offset] == '-') 41 | offset++; 42 | while (token_iter->str()[offset] >= '0' && token_iter->str()[offset] <= '9') 43 | offset++; 44 | 45 | const std::string token = token_iter->str().substr(offset); 46 | const auto token_type_iter = printf_format_types.find(token); 47 | if (token_type_iter == printf_format_types.end()) 48 | { 49 | message << "printf: Unknown format string token: %" << token << std::endl; 50 | return message.str(); 51 | } 52 | const Type &token_type = token_type_iter->second; 53 | 54 | if (arg_type != token_type) 55 | { 56 | message << "printf: %" << token << " specifier expects a value of type " 57 | << token_type << " (" << arg_type << " supplied)" << std::endl; 58 | return message.str(); 59 | } 60 | } 61 | return ""; 62 | } 63 | 64 | } // namespace bpftrace 65 | -------------------------------------------------------------------------------- /src/ast/codegen_llvm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "ast.h" 7 | #include "bpftrace.h" 8 | #include "irbuilderbpf.h" 9 | #include "map.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace bpftrace { 16 | namespace ast { 17 | 18 | using namespace llvm; 19 | 20 | class CodegenLLVM : public Visitor { 21 | public: 22 | explicit CodegenLLVM(Node *root, BPFtrace &bpftrace) : 23 | root_(root), 24 | module_(std::make_unique("bpftrace", context_)), 25 | b_(context_, *module_.get(), bpftrace), 26 | layout_(module_.get()), 27 | bpftrace_(bpftrace) 28 | { } 29 | 30 | void visit(Integer &integer) override; 31 | void visit(String &string) override; 32 | void visit(Builtin &builtin) override; 33 | void visit(Call &call) override; 34 | void visit(Map &map) override; 35 | void visit(Variable &var) override; 36 | void visit(Binop &binop) override; 37 | void visit(Unop &unop) override; 38 | void visit(FieldAccess &acc) override; 39 | void visit(Cast &cast) override; 40 | void visit(ExprStatement &expr) override; 41 | void visit(AssignMapStatement &assignment) override; 42 | void visit(AssignVarStatement &assignment) override; 43 | void visit(Predicate &pred) override; 44 | void visit(AttachPoint &ap) override; 45 | void visit(Probe &probe) override; 46 | void visit(Include &include) override; 47 | void visit(Program &program) override; 48 | AllocaInst *getMapKey(Map &map); 49 | AllocaInst *getQuantizeMapKey(Map &map, Value *log2); 50 | Value *createLogicalAnd(Binop &binop); 51 | Value *createLogicalOr(Binop &binop); 52 | 53 | void createLog2Function(); 54 | void createStrcmpFunction(); 55 | std::unique_ptr compile(bool debug=false, std::ostream &out=std::cerr); 56 | 57 | private: 58 | Node *root_; 59 | LLVMContext context_; 60 | std::unique_ptr module_; 61 | std::unique_ptr ee_; 62 | IRBuilderBPF b_; 63 | DataLayout layout_; 64 | Value *expr_ = nullptr; 65 | Value *ctx_; 66 | BPFtrace &bpftrace_; 67 | 68 | std::map variables_; 69 | }; 70 | 71 | } // namespace ast 72 | } // namespace bpftrace 73 | -------------------------------------------------------------------------------- /src/bpftrace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "common.h" 9 | #include "syms.h" 10 | 11 | #include "ast.h" 12 | #include "attached_probe.h" 13 | #include "imap.h" 14 | #include "struct.h" 15 | #include "types.h" 16 | 17 | namespace bpftrace { 18 | 19 | class BpfOrc; 20 | 21 | class BPFtrace 22 | { 23 | public: 24 | BPFtrace() : ncpus_(ebpf::get_possible_cpus().size()) { } 25 | virtual ~BPFtrace() { } 26 | virtual int add_probe(ast::Probe &p); 27 | int num_probes() const; 28 | int run(std::unique_ptr bpforc); 29 | int print_maps(); 30 | std::string get_stack(uint32_t stackid, bool ustack, int indent=0); 31 | std::string resolve_sym(uintptr_t addr, bool show_offset=false); 32 | std::string resolve_usym(uintptr_t addr) const; 33 | 34 | std::map> maps_; 35 | std::map structs_; 36 | std::vector>> printf_args_; 37 | std::unique_ptr stackid_map_; 38 | std::unique_ptr perf_event_map_; 39 | 40 | static void sort_by_key(std::vector key_args, 41 | std::vector, std::vector>> &values_by_key); 42 | 43 | protected: 44 | virtual std::set find_wildcard_matches(const std::string &prefix, const std::string &attach_point, const std::string &file_name); 45 | std::vector probes_; 46 | std::vector special_probes_; 47 | 48 | private: 49 | std::vector> attached_probes_; 50 | std::vector> special_attached_probes_; 51 | KSyms ksyms_; 52 | int ncpus_; 53 | int online_cpus_; 54 | 55 | std::unique_ptr attach_probe(Probe &probe, const BpfOrc &bpforc); 56 | int setup_perf_events(); 57 | void poll_perf_events(int epollfd, int timeout=-1); 58 | int print_map(IMap &map); 59 | int print_map_quantize(IMap &map); 60 | int print_quantize(const std::vector &values) const; 61 | static uint64_t reduce_value(const std::vector &value, int ncpus); 62 | static std::string quantize_index_label(int power); 63 | std::vector find_empty_key(IMap &map, size_t size) const; 64 | }; 65 | 66 | } // namespace bpftrace 67 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.12) 2 | project(bpftrace) 3 | 4 | add_compile_options("-std=c++14") 5 | add_compile_options("-Wno-format-security") 6 | #add_compile_options("-Wall") 7 | #add_compile_options("-Wextra") 8 | #add_compile_options("-Wundef") 9 | #add_compile_options("-Wpointer-arith") 10 | #add_compile_options("-Wcast-align") 11 | #add_compile_options("-Wwrite-strings") 12 | #add_compile_options("-Wcast-qual") 13 | #add_compile_options("-Wswitch-default") 14 | #add_compile_options("-Wswitch-enum") 15 | #add_compile_options("-Wconversion") 16 | #add_compile_options("-Wunreachable-code") 17 | #add_compile_options("-Wformat=2") 18 | #add_compile_options("-Wstrict-overflow=5") 19 | #add_compile_options("-Wdisabled-optimization") 20 | 21 | enable_testing() 22 | 23 | if (OFFLINE_BUILDS) 24 | include(ExternalProject) 25 | ExternalProject_Add(bcc 26 | GIT_REPOSITORY https://github.com/iovisor/bcc 27 | STEP_TARGETS build update 28 | EXCLUDE_FROM_ALL 1 29 | UPDATE_DISCONNECTED 1 30 | BUILD_COMMAND ${CMAKE_COMMAND} --build . --target bcc-static 31 | ) 32 | else() 33 | include(ExternalProject) 34 | ExternalProject_Add(bcc 35 | GIT_REPOSITORY https://github.com/iovisor/bcc 36 | STEP_TARGETS build update 37 | EXCLUDE_FROM_ALL 1 38 | BUILD_COMMAND ${CMAKE_COMMAND} --build . --target bcc-static 39 | ) 40 | endif() 41 | 42 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 43 | find_package(LibElf REQUIRED) 44 | include_directories(${LIBELF_INCLUDE_DIRS}) 45 | 46 | find_package(BISON REQUIRED) 47 | find_package(FLEX REQUIRED) 48 | bison_target(bison_parser src/parser.yy ${CMAKE_BINARY_DIR}/parser.tab.cc) 49 | flex_target(flex_lexer src/lexer.l ${CMAKE_BINARY_DIR}/lex.yy.cc) 50 | add_flex_bison_dependency(flex_lexer bison_parser) 51 | add_library(parser ${BISON_bison_parser_OUTPUTS} ${FLEX_flex_lexer_OUTPUTS}) 52 | target_include_directories(parser PUBLIC src src/ast ${CMAKE_BINARY_DIR}) 53 | 54 | find_package(LLVM REQUIRED CONFIG) 55 | include_directories(${LLVM_INCLUDE_DIRS}) 56 | add_definitions(${LLVM_DEFINITIONS}) 57 | 58 | set(STATIC_LINKING OFF CACHE BOOL "Build bpftrace as a statically linked executable") 59 | 60 | add_subdirectory(src/arch) 61 | add_subdirectory(src/ast) 62 | add_subdirectory(src) 63 | add_subdirectory(tests) 64 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Linux Kernel 2 | 3 | Your kernel needs to be built with the following options: 4 | ``` 5 | CONFIG_BPF=y 6 | CONFIG_BPF_SYSCALL=y 7 | CONFIG_BPF_JIT=y 8 | CONFIG_HAVE_BPF_JIT=y 9 | CONFIG_BPF_EVENTS=y 10 | ``` 11 | 12 | To use some BPFtrace features, minimum kernel versions are required: 13 | - 4.1+ - kprobes 14 | - 4.3+ - uprobes 15 | - 4.6+ - stack traces, count and quantize builtins (use PERCPU maps for accuracy and efficiency) 16 | - 4.7+ - tracepoints 17 | - 4.9+ - timers/profiling 18 | 19 | 20 | # Building BPFtrace 21 | 22 | ## Native build process 23 | 24 | ### Requirements 25 | 26 | - A C++ compiler 27 | - CMake 28 | - Flex 29 | - Bison 30 | - LLVM & Clang 5.0 development packages 31 | - LibElf 32 | 33 | For example, installing the requirements on Ubuntu: 34 | 35 | ``` 36 | apt-get update 37 | apt-get install -y bison cmake flex g++ git libclang-5.0-dev libelf-dev llvm-5.0-dev zlib1g-dev 38 | ``` 39 | 40 | ### Compilation 41 | 42 | See previous requirements. 43 | 44 | ``` 45 | git clone https://github.com/ajor/bpftrace 46 | mkdir -p bpftrace/build 47 | cd bpftrace/build 48 | cmake -DCMAKE_BUILD_TYPE=Debug ../ 49 | make 50 | ``` 51 | 52 | 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 Docker method below will always result in a statically linked executable. 53 | 54 | The latest versions of BCC and Google Test will be downloaded on each build. To speed up builds and only download their sources on the first run, use the CMake option `-DOFFLINE_BUILDS:BOOL=ON`. 55 | 56 | ## Using Docker 57 | 58 | Building inside a Docker container will produce a statically linked bpftrace executable. 59 | 60 | `./build.sh` 61 | 62 | There are some more fine-grained options if you find yourself building BPFtrace a lot: 63 | - `./build-docker-image.sh` - builds just the `bpftrace-builder` Docker image 64 | - `./build-debug.sh` - builds BPFtrace with debugging information (requires `./build-docker-image.sh` to have already been run) 65 | - `./build-release.sh` - builds BPFtrace in a release configuration (requires `./build-docker-image.sh` to have already been run) 66 | 67 | `./build.sh` is equivalent to `./build-docker-image.sh && ./build-release.sh` 68 | -------------------------------------------------------------------------------- /src/map.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "common.h" 6 | #include "libbpf.h" 7 | 8 | #include "map.h" 9 | 10 | namespace bpftrace { 11 | 12 | Map::Map(const std::string &name, const SizedType &type, const MapKey &key) 13 | { 14 | name_ = name; 15 | type_ = type; 16 | key_ = key; 17 | 18 | int key_size = key.size(); 19 | if (type.type == Type::quantize) 20 | key_size += 8; 21 | if (key_size == 0) 22 | key_size = 8; 23 | 24 | enum bpf_map_type map_type; 25 | if ((type.type == Type::quantize || type.type == Type::count) && 26 | (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0))) 27 | { 28 | map_type = BPF_MAP_TYPE_PERCPU_HASH; 29 | } 30 | else 31 | map_type = BPF_MAP_TYPE_HASH; 32 | 33 | int value_size = type.size; 34 | int max_entries = 128; 35 | int flags = 0; 36 | mapfd_ = bpf_create_map(map_type, name.c_str(), key_size, value_size, max_entries, flags); 37 | if (mapfd_ < 0) 38 | { 39 | std::cerr << "Error creating map: '" << name_ << "'" << std::endl; 40 | } 41 | } 42 | 43 | Map::Map(enum bpf_map_type map_type) 44 | { 45 | int key_size, value_size, max_entries, flags; 46 | 47 | std::string name; 48 | if (map_type == BPF_MAP_TYPE_STACK_TRACE) 49 | { 50 | name = "stack"; 51 | key_size = 4; 52 | value_size = sizeof(uintptr_t) * MAX_STACK_SIZE; 53 | max_entries = 128; 54 | flags = 0; 55 | } 56 | else if (map_type == BPF_MAP_TYPE_PERF_EVENT_ARRAY) 57 | { 58 | std::vector cpus = ebpf::get_online_cpus(); 59 | name = "printf"; 60 | key_size = 4; 61 | value_size = 4; 62 | max_entries = cpus.size(); 63 | flags = 0; 64 | } 65 | else 66 | { 67 | abort(); 68 | } 69 | mapfd_ = bpf_create_map(map_type, name.c_str(), key_size, value_size, max_entries, flags); 70 | if (mapfd_ < 0) 71 | { 72 | std::string name; 73 | switch (map_type) 74 | { 75 | case BPF_MAP_TYPE_STACK_TRACE: 76 | name = "stack id"; 77 | break; 78 | case BPF_MAP_TYPE_PERF_EVENT_ARRAY: 79 | name = "perf event"; 80 | break; 81 | default: 82 | abort(); 83 | } 84 | 85 | std::cerr << "Error creating " << name << " map (" << mapfd_ << ")" << std::endl; 86 | } 87 | } 88 | 89 | Map::~Map() 90 | { 91 | close(mapfd_); 92 | } 93 | 94 | } // namespace bpftrace 95 | -------------------------------------------------------------------------------- /src/bpforc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "llvm/ExecutionEngine/ExecutionEngine.h" 4 | #include "llvm/ExecutionEngine/JITSymbol.h" 5 | #include "llvm/ExecutionEngine/SectionMemoryManager.h" 6 | #include "llvm/ExecutionEngine/Orc/CompileUtils.h" 7 | #include "llvm/ExecutionEngine/Orc/IRCompileLayer.h" 8 | #include "llvm/ExecutionEngine/Orc/LambdaResolver.h" 9 | #include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" 10 | #include "llvm/Target/TargetMachine.h" 11 | 12 | namespace bpftrace { 13 | 14 | using namespace llvm; 15 | using namespace llvm::orc; 16 | 17 | class MemoryManager : public SectionMemoryManager 18 | { 19 | public: 20 | explicit MemoryManager(std::map> §ions) 21 | : sections_(sections) { } 22 | uint8_t *allocateCodeSection(uintptr_t Size, unsigned Alignment, unsigned SectionID, StringRef SectionName) override 23 | { 24 | uint8_t *addr = SectionMemoryManager::allocateCodeSection(Size, Alignment, SectionID, SectionName); 25 | sections_[SectionName.str()] = std::make_tuple(addr, Size); 26 | return addr; 27 | } 28 | 29 | uint8_t *allocateDataSection(uintptr_t Size, unsigned Alignment, unsigned SectionID, StringRef SectionName, bool isReadOnly) override 30 | { 31 | uint8_t *addr = SectionMemoryManager::allocateDataSection(Size, Alignment, SectionID, SectionName, isReadOnly); 32 | sections_[SectionName.str()] = std::make_tuple(addr, Size); 33 | return addr; 34 | } 35 | 36 | private: 37 | std::map> §ions_; 38 | }; 39 | 40 | class BpfOrc 41 | { 42 | private: 43 | std::unique_ptr TM; 44 | RTDyldObjectLinkingLayer ObjectLayer; 45 | IRCompileLayer CompileLayer; 46 | 47 | public: 48 | std::map> sections_; 49 | 50 | using ModuleHandle = decltype(CompileLayer)::ModuleHandleT; 51 | 52 | BpfOrc(TargetMachine *TM_) 53 | : TM(TM_), 54 | ObjectLayer([this]() { return std::make_shared(sections_); }), 55 | CompileLayer(ObjectLayer, SimpleCompiler(*TM)) 56 | { 57 | } 58 | 59 | void compileModule(std::unique_ptr M) 60 | { 61 | auto mod = addModule(move(M)); 62 | CompileLayer.emitAndFinalize(mod); 63 | } 64 | 65 | ModuleHandle addModule(std::unique_ptr M) { 66 | // We don't actually care about resolving symbols from other modules 67 | auto Resolver = createLambdaResolver( 68 | [](const std::string &Name) { return JITSymbol(nullptr); }, 69 | [](const std::string &Name) { return JITSymbol(nullptr); }); 70 | 71 | return cantFail(CompileLayer.addModule(std::move(M), std::move(Resolver))); 72 | } 73 | }; 74 | 75 | } // namespace bpftrace 76 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (STATIC_LINKING) 2 | set(CMAKE_EXE_LINKER_FLAGS "-static") 3 | endif() 4 | 5 | add_compile_options("-Wno-undef") 6 | add_compile_options("-Wno-switch-default") 7 | add_compile_options("-Wno-switch-enum") 8 | 9 | add_executable(bpftrace_test 10 | ast.cpp 11 | bpftrace.cpp 12 | codegen.cpp 13 | main.cpp 14 | parser.cpp 15 | semantic_analyser.cpp 16 | ${CMAKE_SOURCE_DIR}/src/attached_probe.cpp 17 | ${CMAKE_SOURCE_DIR}/src/bpftrace.cpp 18 | ${CMAKE_SOURCE_DIR}/src/driver.cpp 19 | ${CMAKE_SOURCE_DIR}/src/fake_map.cpp 20 | ${CMAKE_SOURCE_DIR}/src/map.cpp 21 | ${CMAKE_SOURCE_DIR}/src/mapkey.cpp 22 | ${CMAKE_SOURCE_DIR}/src/printf.cpp 23 | ${CMAKE_SOURCE_DIR}/src/types.cpp 24 | ${CMAKE_SOURCE_DIR}/src/ast/ast.cpp 25 | ${CMAKE_SOURCE_DIR}/src/ast/codegen_llvm.cpp 26 | ${CMAKE_SOURCE_DIR}/src/ast/irbuilderbpf.cpp 27 | ${CMAKE_SOURCE_DIR}/src/ast/printer.cpp 28 | ${CMAKE_SOURCE_DIR}/src/ast/semantic_analyser.cpp 29 | ) 30 | 31 | target_link_libraries(bpftrace_test arch parser) 32 | 33 | llvm_map_components_to_libnames(llvm_libs bpfcodegen ipo irreader mcjit) 34 | target_link_libraries(bpftrace_test ${llvm_libs}) 35 | 36 | add_dependencies(bpftrace_test bcc-build) 37 | ExternalProject_Get_Property(bcc source_dir binary_dir) 38 | target_include_directories(bpftrace_test PUBLIC ${source_dir}/src/cc) 39 | target_link_libraries(bpftrace_test ${binary_dir}/src/cc/libbpf.a) 40 | target_link_libraries(bpftrace_test ${binary_dir}/src/cc/libbcc-loader-static.a) 41 | target_link_libraries(bpftrace_test ${binary_dir}/src/cc/libbcc.a) 42 | target_link_libraries(bpftrace_test ${LIBELF_LIBRARIES}) 43 | 44 | find_package(Threads REQUIRED) 45 | 46 | if (OFFLINE_BUILDS) 47 | include(ExternalProject) 48 | ExternalProject_Add(gtest 49 | GIT_REPOSITORY https://github.com/google/googletest.git 50 | STEP_TARGETS build update 51 | EXCLUDE_FROM_ALL 1 52 | UPDATE_DISCONNECTED 1 53 | ) 54 | else() 55 | include(ExternalProject) 56 | ExternalProject_Add(gtest 57 | GIT_REPOSITORY https://github.com/google/googletest.git 58 | STEP_TARGETS build update 59 | EXCLUDE_FROM_ALL 1 60 | ) 61 | endif() 62 | add_dependencies(bpftrace_test gtest-build) 63 | ExternalProject_Get_Property(gtest source_dir binary_dir) 64 | target_include_directories(bpftrace_test PUBLIC ${source_dir}/googletest/include) 65 | target_include_directories(bpftrace_test PUBLIC ${source_dir}/googlemock/include) 66 | target_link_libraries(bpftrace_test ${binary_dir}/googlemock/gtest/libgtest.a) 67 | target_link_libraries(bpftrace_test ${binary_dir}/googlemock/gtest/libgtest_main.a) 68 | target_link_libraries(bpftrace_test ${binary_dir}/googlemock/libgmock.a) 69 | target_link_libraries(bpftrace_test ${CMAKE_THREAD_LIBS_INIT}) 70 | 71 | add_test(NAME bpftrace_test COMMAND bpftrace_test) 72 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "bpforc.h" 5 | #include "bpftrace.h" 6 | #include "codegen_llvm.h" 7 | #include "driver.h" 8 | #include "printer.h" 9 | #include "semantic_analyser.h" 10 | 11 | using namespace bpftrace; 12 | 13 | void usage() 14 | { 15 | std::cerr << "Usage:" << std::endl; 16 | std::cerr << " bpftrace filename" << std::endl; 17 | std::cerr << " bpftrace -e 'script'" << std::endl; 18 | } 19 | 20 | int main(int argc, char *argv[]) 21 | { 22 | int err; 23 | Driver driver; 24 | 25 | std::string script; 26 | bool debug = false; 27 | int c; 28 | while ((c = getopt(argc, argv, "de:")) != -1) 29 | { 30 | switch (c) 31 | { 32 | case 'd': 33 | debug = true; 34 | break; 35 | case 'e': 36 | script = optarg; 37 | break; 38 | default: 39 | usage(); 40 | return 1; 41 | } 42 | } 43 | 44 | if (script.empty()) 45 | { 46 | // There should only be 1 non-option argument (the script file) 47 | if (optind != argc-1) 48 | { 49 | usage(); 50 | return 1; 51 | } 52 | char *file_name = argv[optind]; 53 | err = driver.parse_file(file_name); 54 | } 55 | else 56 | { 57 | // Script is provided as a command line argument 58 | if (optind != argc) 59 | { 60 | usage(); 61 | return 1; 62 | } 63 | err = driver.parse_str(script); 64 | } 65 | 66 | if (err) 67 | return err; 68 | 69 | BPFtrace bpftrace; 70 | 71 | if (debug) 72 | { 73 | ast::Printer p(std::cout); 74 | driver.root_->accept(p); 75 | } 76 | 77 | ast::SemanticAnalyser semantics(driver.root_, bpftrace); 78 | err = semantics.analyse(); 79 | if (err) 80 | return err; 81 | 82 | err = semantics.create_maps(debug); 83 | if (err) 84 | return err; 85 | 86 | ast::CodegenLLVM llvm(driver.root_, bpftrace); 87 | auto bpforc = llvm.compile(debug); 88 | 89 | if (debug) 90 | return 0; 91 | 92 | // Empty signal handler for cleanly terminating the program 93 | struct sigaction act; 94 | act.sa_handler = [](int) { }; 95 | sigaction(SIGINT, &act, NULL); 96 | 97 | int num_probes = bpftrace.num_probes(); 98 | if (num_probes == 0) 99 | { 100 | std::cout << "No probes to attach" << std::endl; 101 | return 1; 102 | } 103 | else if (num_probes == 1) 104 | std::cout << "Attaching " << bpftrace.num_probes() << " probe..." << std::endl; 105 | else 106 | std::cout << "Attaching " << bpftrace.num_probes() << " probes..." << std::endl; 107 | 108 | err = bpftrace.run(move(bpforc)); 109 | if (err) 110 | return err; 111 | 112 | std::cout << "\n\n"; 113 | 114 | err = bpftrace.print_maps(); 115 | if (err) 116 | return err; 117 | 118 | return 0; 119 | } 120 | -------------------------------------------------------------------------------- /src/ast/ast.cpp: -------------------------------------------------------------------------------- 1 | #include "ast.h" 2 | #include "parser.tab.hh" 3 | 4 | namespace bpftrace { 5 | namespace ast { 6 | 7 | void Integer::accept(Visitor &v) { 8 | v.visit(*this); 9 | } 10 | 11 | void String::accept(Visitor &v) { 12 | v.visit(*this); 13 | } 14 | 15 | void Builtin::accept(Visitor &v) { 16 | v.visit(*this); 17 | } 18 | 19 | void Call::accept(Visitor &v) { 20 | v.visit(*this); 21 | } 22 | 23 | void Map::accept(Visitor &v) { 24 | v.visit(*this); 25 | } 26 | 27 | void Variable::accept(Visitor &v) { 28 | v.visit(*this); 29 | } 30 | 31 | void Binop::accept(Visitor &v) { 32 | v.visit(*this); 33 | } 34 | 35 | void Unop::accept(Visitor &v) { 36 | v.visit(*this); 37 | } 38 | 39 | void FieldAccess::accept(Visitor &v) { 40 | v.visit(*this); 41 | } 42 | 43 | void Cast::accept(Visitor &v) { 44 | v.visit(*this); 45 | } 46 | 47 | void ExprStatement::accept(Visitor &v) { 48 | v.visit(*this); 49 | } 50 | 51 | void AssignMapStatement::accept(Visitor &v) { 52 | v.visit(*this); 53 | } 54 | 55 | void AssignVarStatement::accept(Visitor &v) { 56 | v.visit(*this); 57 | } 58 | 59 | void Predicate::accept(Visitor &v) { 60 | v.visit(*this); 61 | } 62 | 63 | void AttachPoint::accept(Visitor &v) { 64 | v.visit(*this); 65 | } 66 | 67 | void Probe::accept(Visitor &v) { 68 | v.visit(*this); 69 | } 70 | 71 | void Include::accept(Visitor &v) { 72 | v.visit(*this); 73 | } 74 | 75 | void Program::accept(Visitor &v) { 76 | v.visit(*this); 77 | } 78 | 79 | std::string opstr(Binop &binop) 80 | { 81 | switch (binop.op) { 82 | case bpftrace::Parser::token::EQ: return "=="; 83 | case bpftrace::Parser::token::NE: return "!="; 84 | case bpftrace::Parser::token::LE: return "<="; 85 | case bpftrace::Parser::token::GE: return ">="; 86 | case bpftrace::Parser::token::LT: return "<"; 87 | case bpftrace::Parser::token::GT: return ">"; 88 | case bpftrace::Parser::token::LAND: return "&&"; 89 | case bpftrace::Parser::token::LOR: return "||"; 90 | case bpftrace::Parser::token::PLUS: return "+"; 91 | case bpftrace::Parser::token::MINUS: return "-"; 92 | case bpftrace::Parser::token::MUL: return "*"; 93 | case bpftrace::Parser::token::DIV: return "/"; 94 | case bpftrace::Parser::token::MOD: return "%"; 95 | case bpftrace::Parser::token::BAND: return "&"; 96 | case bpftrace::Parser::token::BOR: return "|"; 97 | case bpftrace::Parser::token::BXOR: return "^"; 98 | default: abort(); 99 | } 100 | } 101 | 102 | std::string opstr(Unop &unop) 103 | { 104 | switch (unop.op) { 105 | case bpftrace::Parser::token::LNOT: return "!"; 106 | case bpftrace::Parser::token::BNOT: return "~"; 107 | case bpftrace::Parser::token::MUL: return "dereference"; 108 | default: abort(); 109 | } 110 | } 111 | 112 | std::string AttachPoint::name(const std::string &attach_point) const 113 | { 114 | std::string n = provider; 115 | if (target != "") 116 | n += ":" + target; 117 | if (attach_point != "") 118 | n += ":" + attach_point; 119 | if (freq != 0) 120 | n += ":" + std::to_string(freq); 121 | return n; 122 | } 123 | 124 | std::string Probe::name() const 125 | { 126 | std::string n = ""; 127 | for (auto attach_point : *attach_points) 128 | { 129 | n += attach_point->provider; 130 | if (attach_point->target != "") 131 | n += ":" + attach_point->target; 132 | if (attach_point->func != "") 133 | n += ":" + attach_point->func; 134 | if (attach_point->freq != 0) 135 | n += ":" + std::to_string(attach_point->freq); 136 | n += ","; 137 | } 138 | return n.substr(0, n.size()-1); 139 | } 140 | 141 | } // namespace ast 142 | } // namespace bpftrace 143 | -------------------------------------------------------------------------------- /src/ast/printer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "printer.h" 4 | #include "ast.h" 5 | 6 | namespace bpftrace { 7 | namespace ast { 8 | 9 | void Printer::visit(Integer &integer) 10 | { 11 | std::string indent(depth_, ' '); 12 | out_ << indent << "int: " << integer.n << std::endl; 13 | } 14 | 15 | void Printer::visit(String &string) 16 | { 17 | std::string indent(depth_, ' '); 18 | 19 | std::string str = string.str; 20 | str = std::regex_replace(str, std::regex("\\\\"), "\\\\"); 21 | str = std::regex_replace(str, std::regex("\n"), "\\n"); 22 | str = std::regex_replace(str, std::regex("\t"), "\\t"); 23 | str = std::regex_replace(str, std::regex("\""), "\\\""); 24 | 25 | out_ << indent << "string: " << str << std::endl; 26 | } 27 | 28 | void Printer::visit(Builtin &builtin) 29 | { 30 | std::string indent(depth_, ' '); 31 | out_ << indent << "builtin: " << builtin.ident << std::endl; 32 | } 33 | 34 | void Printer::visit(Call &call) 35 | { 36 | std::string indent(depth_, ' '); 37 | out_ << indent << "call: " << call.func << std::endl; 38 | 39 | ++depth_; 40 | if (call.vargs) { 41 | for (Expression *expr : *call.vargs) { 42 | expr->accept(*this); 43 | } 44 | } 45 | --depth_; 46 | } 47 | 48 | void Printer::visit(Map &map) 49 | { 50 | std::string indent(depth_, ' '); 51 | out_ << indent << "map: " << map.ident << std::endl; 52 | 53 | ++depth_; 54 | if (map.vargs) { 55 | for (Expression *expr : *map.vargs) { 56 | expr->accept(*this); 57 | } 58 | } 59 | --depth_; 60 | } 61 | 62 | void Printer::visit(Variable &var) 63 | { 64 | std::string indent(depth_, ' '); 65 | out_ << indent << "variable: " << var.ident << std::endl; 66 | } 67 | 68 | void Printer::visit(Binop &binop) 69 | { 70 | std::string indent(depth_, ' '); 71 | out_ << indent << opstr(binop) << std::endl; 72 | 73 | ++depth_; 74 | binop.left->accept(*this); 75 | binop.right->accept(*this); 76 | --depth_; 77 | } 78 | 79 | void Printer::visit(Unop &unop) 80 | { 81 | std::string indent(depth_, ' '); 82 | out_ << indent << opstr(unop) << std::endl; 83 | 84 | ++depth_; 85 | unop.expr->accept(*this); 86 | --depth_; 87 | } 88 | 89 | void Printer::visit(FieldAccess &acc) 90 | { 91 | std::string indent(depth_, ' '); 92 | out_ << indent << "." << std::endl; 93 | 94 | ++depth_; 95 | acc.expr->accept(*this); 96 | --depth_; 97 | 98 | out_ << indent << " " << acc.field << std::endl; 99 | } 100 | 101 | void Printer::visit(Cast &cast) 102 | { 103 | std::string indent(depth_, ' '); 104 | out_ << indent << "(" << cast.cast_type << ")" << std::endl; 105 | 106 | ++depth_; 107 | cast.expr->accept(*this); 108 | --depth_; 109 | } 110 | 111 | void Printer::visit(ExprStatement &expr) 112 | { 113 | expr.expr->accept(*this); 114 | } 115 | 116 | void Printer::visit(AssignMapStatement &assignment) 117 | { 118 | std::string indent(depth_, ' '); 119 | out_ << indent << "=" << std::endl; 120 | 121 | ++depth_; 122 | assignment.map->accept(*this); 123 | assignment.expr->accept(*this); 124 | --depth_; 125 | } 126 | 127 | void Printer::visit(AssignVarStatement &assignment) 128 | { 129 | std::string indent(depth_, ' '); 130 | out_ << indent << "=" << std::endl; 131 | 132 | ++depth_; 133 | assignment.var->accept(*this); 134 | assignment.expr->accept(*this); 135 | --depth_; 136 | } 137 | 138 | void Printer::visit(Predicate &pred) 139 | { 140 | std::string indent(depth_, ' '); 141 | out_ << indent << "pred" << std::endl; 142 | 143 | ++depth_; 144 | pred.expr->accept(*this); 145 | --depth_; 146 | } 147 | 148 | void Printer::visit(AttachPoint &ap) 149 | { 150 | std::string indent(depth_, ' '); 151 | out_ << indent << ap.name(ap.func) << std::endl; 152 | } 153 | 154 | void Printer::visit(Probe &probe) 155 | { 156 | for (AttachPoint *ap : *probe.attach_points) { 157 | ap->accept(*this); 158 | } 159 | 160 | ++depth_; 161 | if (probe.pred) { 162 | probe.pred->accept(*this); 163 | } 164 | for (Statement *stmt : *probe.stmts) { 165 | stmt->accept(*this); 166 | } 167 | --depth_; 168 | } 169 | 170 | void Printer::visit(Include &include) 171 | { 172 | std::string indent(depth_, ' '); 173 | if (include.system_header) 174 | out_ << indent << "#include <" << include.file << ">" << std::endl; 175 | else 176 | out_ << indent << "#include \"" << include.file << "\"" << std::endl; 177 | } 178 | 179 | void Printer::visit(Program &program) 180 | { 181 | std::string indent(depth_, ' '); 182 | out_ << indent << "Program" << std::endl; 183 | 184 | ++depth_; 185 | for (Include *include : *program.includes) 186 | include->accept(*this); 187 | for (Probe *probe : *program.probes) 188 | probe->accept(*this); 189 | --depth_; 190 | } 191 | 192 | } // namespace ast 193 | } // namespace bpftrace 194 | -------------------------------------------------------------------------------- /src/lexer.l: -------------------------------------------------------------------------------- 1 | %option yylineno nodefault noyywrap noinput nounput 2 | %option never-interactive 3 | %option reentrant 4 | %{ 5 | #include "driver.h" 6 | #include "parser.tab.hh" 7 | 8 | #undef yywrap 9 | #define yywrap(x) 1 10 | 11 | static bpftrace::location loc; 12 | static std::string string_buffer; 13 | 14 | #define YY_USER_ACTION loc.columns(yyleng); 15 | #define yyterminate() return bpftrace::Parser::make_END(loc) 16 | 17 | using namespace bpftrace; 18 | %} 19 | 20 | ident [_a-zA-Z][_a-zA-Z0-9]* 21 | map @{ident}|@ 22 | var ${ident} 23 | int [0-9]+|0[xX][0-9a-fA-F]+ 24 | hspace [ \t] 25 | vspace [\n\r] 26 | space {hspace}|{vspace} 27 | path :(\\.|[_\-\./a-zA-Z0-9])*: 28 | header <(\\.|[_\-\./a-zA-Z0-9])*> 29 | %x STR 30 | 31 | %% 32 | 33 | %{ 34 | loc.step(); 35 | %} 36 | 37 | {hspace}+ { loc.step(); } 38 | {vspace}+ { loc.lines(yyleng); loc.step(); } 39 | "//".*$ // Comments 40 | 41 | pid|tid|uid|gid|nsecs|cpu|comm|stack|ustack|arg[0-9]|retval|func { 42 | return Parser::make_BUILTIN(yytext, loc); } 43 | {ident} { return Parser::make_IDENT(yytext, loc); } 44 | {path} { return Parser::make_PATH(yytext, loc); } 45 | {header} { return Parser::make_HEADER(yytext, loc); } 46 | {map} { return Parser::make_MAP(yytext, loc); } 47 | {var} { return Parser::make_VAR(yytext, loc); } 48 | {int} { return Parser::make_INT(strtoul(yytext, NULL, 0), loc); } 49 | ":" { return Parser::make_COLON(loc); } 50 | ";" { return Parser::make_SEMI(loc); } 51 | "{" { return Parser::make_LBRACE(loc); } 52 | "}" { return Parser::make_RBRACE(loc); } 53 | "[" { return Parser::make_LBRACKET(loc); } 54 | "]" { return Parser::make_RBRACKET(loc); } 55 | "(" { return Parser::make_LPAREN(loc); } 56 | ")" { return Parser::make_RPAREN(loc); } 57 | \//{space}*[\/\{] { return Parser::make_ENDPRED(loc); } // If "/" is followed by "/" or "{", choose ENDPRED, otherwise DIV 58 | "," { return Parser::make_COMMA(loc); } 59 | "=" { return Parser::make_ASSIGN(loc); } 60 | "==" { return Parser::make_EQ(loc); } 61 | "!=" { return Parser::make_NE(loc); } 62 | "<=" { return Parser::make_LE(loc); } 63 | ">=" { return Parser::make_GE(loc); } 64 | "<" { return Parser::make_LT(loc); } 65 | ">" { return Parser::make_GT(loc); } 66 | "&&" { return Parser::make_LAND(loc); } 67 | "||" { return Parser::make_LOR(loc); } 68 | "+" { return Parser::make_PLUS(loc); } 69 | "-" { return Parser::make_MINUS(loc); } 70 | "*" { return Parser::make_MUL(loc); } 71 | "/" { return Parser::make_DIV(loc); } 72 | "%" { return Parser::make_MOD(loc); } 73 | "&" { return Parser::make_BAND(loc); } 74 | "|" { return Parser::make_BOR(loc); } 75 | "^" { return Parser::make_BXOR(loc); } 76 | "!" { return Parser::make_LNOT(loc); } 77 | "~" { return Parser::make_BNOT(loc); } 78 | "#include" { return Parser::make_INCLUDE(loc); } 79 | "." { return Parser::make_DOT(loc); } 80 | "->" { return Parser::make_PTR(loc); } 81 | 82 | \" { BEGIN(STR); string_buffer.clear(); } 83 | \" { BEGIN(INITIAL); return Parser::make_STRING(string_buffer, loc); } 84 | [^\\\n\"]+ { string_buffer += std::string(yytext); } 85 | \\n { string_buffer += std::string("\n"); } 86 | \\t { string_buffer += std::string("\t"); } 87 | \\\" { string_buffer += std::string("\""); } 88 | \\\\ { string_buffer += std::string("\\"); } 89 | \n { driver.error(loc, std::string("unterminated string")); } 90 | <> { driver.error(loc, std::string("unterminated string")); } 91 | \\. { driver.error(loc, std::string("invalid escape character '") + 92 | std::string(yytext) + std::string("'")); } 93 | . { driver.error(loc, std::string("invalid character '") + 94 | std::string(yytext) + std::string("'")); } 95 | 96 | . { driver.error(loc, std::string("invalid character '") + 97 | std::string(yytext) + std::string("'")); } 98 | 99 | %% 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # New repository https://github.com/iovisor/bpftrace 2 | 3 | Development of BPFtrace has moved to https://github.com/iovisor/bpftrace 4 | 5 | This repository will no longer be updated. 6 | 7 | # BPFtrace 8 | 9 | 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. 10 | 11 | For instructions on building BPFtrace, see [INSTALL.md](INSTALL.md) 12 | 13 | ## Examples 14 | 15 | Count system calls: 16 | ``` 17 | kprobe:[Ss]y[Ss]_* 18 | { 19 | @[func] = count() 20 | } 21 | ``` 22 | ``` 23 | Attaching 376 probes... 24 | ^C 25 | 26 | ... 27 | @[sys_open]: 579 28 | @[SyS_ioctl]: 686 29 | @[sys_bpf]: 730 30 | @[sys_close]: 779 31 | @[SyS_read]: 825 32 | @[sys_write]: 1031 33 | @[sys_poll]: 1796 34 | @[sys_futex]: 2237 35 | @[sys_recvmsg]: 2634 36 | ``` 37 | 38 | Produce a histogram of amount of time (in nanoseconds) spent in the `read()` system call: 39 | ``` 40 | kprobe:sys_read 41 | { 42 | @start[tid] = nsecs; 43 | } 44 | 45 | kretprobe:sys_read / @start[tid] / 46 | { 47 | @times = quantize(nsecs - @start[tid]); 48 | delete(@start[tid]); 49 | } 50 | ``` 51 | ``` 52 | Attaching 2 probes... 53 | ^C 54 | 55 | @start[9134]: 6465933686812 56 | 57 | @times: 58 | [0, 1] 0 | | 59 | [2, 4) 0 | | 60 | [4, 8) 0 | | 61 | [8, 16) 0 | | 62 | [16, 32) 0 | | 63 | [32, 64) 0 | | 64 | [64, 128) 0 | | 65 | [128, 256) 0 | | 66 | [256, 512) 326 |@ | 67 | [512, 1k) 7715 |@@@@@@@@@@@@@@@@@@@@@@@@@@ | 68 | [1k, 2k) 15306 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| 69 | [2k, 4k) 609 |@@ | 70 | [4k, 8k) 611 |@@ | 71 | [8k, 16k) 438 |@ | 72 | [16k, 32k) 59 | | 73 | [32k, 64k) 36 | | 74 | [64k, 128k) 5 | | 75 | ``` 76 | 77 | Print paths of any files opened along with the name of process which opened them: 78 | ``` 79 | kprobe:sys_open 80 | { 81 | printf("%s: %s\n", comm, str(arg0)) 82 | } 83 | ``` 84 | ``` 85 | Attaching 1 probe... 86 | git: .git/objects/70 87 | git: .git/objects/pack 88 | git: .git/objects/da 89 | git: .git/objects/pack 90 | git: /etc/localtime 91 | systemd-journal: /var/log/journal/72d0774c88dc4943ae3d34ac356125dd 92 | DNS Res~ver #15: /etc/hosts 93 | DNS Res~ver #16: /etc/hosts 94 | DNS Res~ver #15: /etc/hosts 95 | ^C 96 | ``` 97 | 98 | Whole system profiling (TODO make example check if kernel is on-cpu before recording): 99 | ``` 100 | profile:hz:99 101 | { 102 | @[stack] = count() 103 | } 104 | ``` 105 | ``` 106 | Attaching 1 probe... 107 | ^C 108 | 109 | ... 110 | @[ 111 | _raw_spin_unlock_irq+23 112 | finish_task_switch+117 113 | __schedule+574 114 | schedule_idle+44 115 | do_idle+333 116 | cpu_startup_entry+113 117 | start_secondary+344 118 | verify_cpu+0 119 | ]: 83 120 | @[ 121 | queue_work_on+41 122 | tty_flip_buffer_push+43 123 | pty_write+83 124 | n_tty_write+434 125 | tty_write+444 126 | __vfs_write+55 127 | vfs_write+177 128 | sys_write+85 129 | entry_SYSCALL_64_fastpath+26 130 | ]: 97 131 | @[ 132 | cpuidle_enter_state+299 133 | cpuidle_enter+23 134 | call_cpuidle+35 135 | do_idle+394 136 | cpu_startup_entry+113 137 | rest_init+132 138 | start_kernel+1083 139 | x86_64_start_reservations+41 140 | x86_64_start_kernel+323 141 | verify_cpu+0 142 | ]: 150 143 | ``` 144 | 145 | ## Probe types 146 | 147 | ### kprobes 148 | Attach a BPFtrace script to a kernel function, to be executed when that function is called: 149 | 150 | `kprobe:sys_read { ... }` 151 | 152 | ### uprobes 153 | Attach script to a userland function: 154 | 155 | `uprobe:/bin/bash:readline { ... }` 156 | 157 | ### tracepoints 158 | Attach script to a statically defined tracepoint in the kernel: 159 | 160 | `tracepoint:sched:sched_switch { ... }` 161 | 162 | Tracepoints are guaranteed to be stable between kernel versions, unlike kprobes. 163 | 164 | ### timers 165 | Run the script at specified time intervals: 166 | 167 | `profile:hz:99 { ... }` 168 | 169 | `profile:s:1 { ... }` 170 | 171 | `profile:ms:20 { ... }` 172 | 173 | `profile:us:1500 { ... }` 174 | 175 | ### Multiple attachment points 176 | A single probe can be attached to multiple events: 177 | 178 | `kprobe:sys_read,kprobe:sys_write { ... }` 179 | 180 | ### Wildcards 181 | Some probe types allow wildcards to be used when attaching a probe: 182 | 183 | `kprobe:SyS_* { ... }` 184 | 185 | ### Predicates 186 | Define conditions for which a probe should be executed: 187 | 188 | `kprobe:sys_open / uid == 0 / { ... }` 189 | 190 | ## Builtins 191 | The following variables and functions are available for use in bpftrace scripts: 192 | 193 | Variables: 194 | - `pid` - Process ID (kernel tgid) 195 | - `tid` - Thread ID (kernel pid) 196 | - `uid` - User ID 197 | - `gid` - Group ID 198 | - `nsecs` - Nanosecond timestamp 199 | - `cpu` - Processor ID 200 | - `comm` - Process name 201 | - `stack` - Kernel stack trace 202 | - `ustack` - User stack trace 203 | - `arg0`, `arg1`, ... etc. - Arguments to the function being traced 204 | - `retval` - Return value from function being traced 205 | - `func` - Name of the function currently being traced 206 | 207 | Functions: 208 | - `quantize(int n)` - Produce a log2 histogram of values of `n` 209 | - `count()` - Count the number of times this function is called 210 | - `delete(@x)` - Delete the map element passed in as an argument 211 | - `str(char *s)` - Returns the string pointed to by `s` 212 | - `printf(char *fmt, ...)` - Write to stdout 213 | - `sym(void *p)` - Resolve kernel address 214 | - `usym(void *p)` - Resolve user space address (incomplete) 215 | - `reg(char *name)` - Returns the value stored in the named register 216 | -------------------------------------------------------------------------------- /src/ast/ast.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "types.h" 7 | 8 | namespace bpftrace { 9 | namespace ast { 10 | 11 | class Visitor; 12 | 13 | class Node { 14 | public: 15 | virtual ~Node() { } 16 | virtual void accept(Visitor &v) = 0; 17 | }; 18 | 19 | class Map; 20 | class Variable; 21 | class Expression : public Node { 22 | public: 23 | SizedType type; 24 | Map *map = nullptr; // Only set when this expression is assigned to a map 25 | Variable *var = nullptr; // Set when this expression is assigned to a variable 26 | bool is_literal = false; 27 | bool is_variable = false; 28 | bool is_map = false; 29 | }; 30 | using ExpressionList = std::vector; 31 | 32 | class Integer : public Expression { 33 | public: 34 | explicit Integer(int n) : n(n) { is_literal = true; } 35 | int n; 36 | 37 | void accept(Visitor &v) override; 38 | }; 39 | 40 | class String : public Expression { 41 | public: 42 | explicit String(std::string str) : str(str) { is_literal = true; } 43 | std::string str; 44 | 45 | void accept(Visitor &v) override; 46 | }; 47 | 48 | class Builtin : public Expression { 49 | public: 50 | explicit Builtin(std::string ident) : ident(ident) { } 51 | std::string ident; 52 | 53 | void accept(Visitor &v) override; 54 | }; 55 | 56 | class Call : public Expression { 57 | public: 58 | explicit Call(std::string &func) : func(func), vargs(nullptr) { } 59 | Call(std::string &func, ExpressionList *vargs) : func(func), vargs(vargs) { } 60 | std::string func; 61 | ExpressionList *vargs; 62 | 63 | void accept(Visitor &v) override; 64 | }; 65 | 66 | class Map : public Expression { 67 | public: 68 | explicit Map(std::string &ident) : ident(ident), vargs(nullptr) { is_map = true; } 69 | Map(std::string &ident, ExpressionList *vargs) : ident(ident), vargs(vargs) { is_map = true; } 70 | std::string ident; 71 | ExpressionList *vargs; 72 | 73 | void accept(Visitor &v) override; 74 | }; 75 | 76 | class Variable : public Expression { 77 | public: 78 | explicit Variable(std::string &ident) : ident(ident) { is_variable = true; } 79 | std::string ident; 80 | 81 | void accept(Visitor &v) override; 82 | }; 83 | 84 | class Binop : public Expression { 85 | public: 86 | Binop(Expression *left, int op, Expression *right) : left(left), right(right), op(op) { } 87 | Expression *left, *right; 88 | int op; 89 | 90 | void accept(Visitor &v) override; 91 | }; 92 | 93 | class Unop : public Expression { 94 | public: 95 | Unop(int op, Expression *expr) : expr(expr), op(op) { } 96 | Expression *expr; 97 | int op; 98 | 99 | void accept(Visitor &v) override; 100 | }; 101 | 102 | class FieldAccess : public Expression { 103 | public: 104 | FieldAccess(Expression *expr, const std::string &field) : expr(expr), field(field) { } 105 | Expression *expr; 106 | std::string field; 107 | 108 | void accept(Visitor &v) override; 109 | }; 110 | 111 | class Cast : public Expression { 112 | public: 113 | Cast(const std::string &type, Expression *expr) : cast_type(type), expr(expr) { } 114 | std::string cast_type; 115 | Expression *expr; 116 | 117 | void accept(Visitor &v) override; 118 | }; 119 | 120 | class Statement : public Node { 121 | }; 122 | using StatementList = std::vector; 123 | 124 | class ExprStatement : public Statement { 125 | public: 126 | explicit ExprStatement(Expression *expr) : expr(expr) { } 127 | Expression *expr; 128 | 129 | void accept(Visitor &v) override; 130 | }; 131 | 132 | class AssignMapStatement : public Statement { 133 | public: 134 | AssignMapStatement(Map *map, Expression *expr) : map(map), expr(expr) { 135 | expr->map = map; 136 | } 137 | Map *map; 138 | Expression *expr; 139 | 140 | void accept(Visitor &v) override; 141 | }; 142 | 143 | class AssignVarStatement : public Statement { 144 | public: 145 | AssignVarStatement(Variable *var, Expression *expr) : var(var), expr(expr) { 146 | expr->var = var; 147 | } 148 | Variable *var; 149 | Expression *expr; 150 | 151 | void accept(Visitor &v) override; 152 | }; 153 | 154 | class Predicate : public Node { 155 | public: 156 | explicit Predicate(Expression *expr) : expr(expr) { } 157 | Expression *expr; 158 | 159 | void accept(Visitor &v) override; 160 | }; 161 | 162 | class AttachPoint : public Node { 163 | public: 164 | explicit AttachPoint(const std::string &provider) 165 | : provider(provider) { } 166 | AttachPoint(const std::string &provider, 167 | const std::string &func) 168 | : provider(provider), func(func) { } 169 | AttachPoint(const std::string &provider, 170 | const std::string &target, 171 | const std::string &func) 172 | : provider(provider), target(target), func(func) { } 173 | AttachPoint(const std::string &provider, 174 | const std::string &target, 175 | int freq) 176 | : provider(provider), target(target), freq(freq) { } 177 | 178 | std::string provider; 179 | std::string target; 180 | std::string func; 181 | int freq = 0; 182 | 183 | void accept(Visitor &v) override; 184 | std::string name(const std::string &attach_point) const; 185 | }; 186 | using AttachPointList = std::vector; 187 | 188 | class Probe : public Node { 189 | public: 190 | Probe(AttachPointList *attach_points, Predicate *pred, StatementList *stmts) 191 | : attach_points(attach_points), pred(pred), stmts(stmts) { } 192 | 193 | AttachPointList *attach_points; 194 | Predicate *pred; 195 | StatementList *stmts; 196 | 197 | void accept(Visitor &v) override; 198 | std::string name() const; 199 | }; 200 | using ProbeList = std::vector; 201 | 202 | class Include : public Node { 203 | public: 204 | explicit Include(const std::string &file, bool system_header) 205 | : file(file), system_header(system_header) { } 206 | std::string file; 207 | bool system_header; 208 | 209 | void accept(Visitor &v) override; 210 | }; 211 | using IncludeList = std::vector; 212 | 213 | class Program : public Node { 214 | public: 215 | Program(IncludeList *includes, ProbeList *probes) 216 | : includes(includes), probes(probes) { } 217 | IncludeList *includes; 218 | ProbeList *probes; 219 | 220 | void accept(Visitor &v) override; 221 | }; 222 | 223 | class Visitor { 224 | public: 225 | virtual ~Visitor() { } 226 | virtual void visit(Integer &integer) = 0; 227 | virtual void visit(String &string) = 0; 228 | virtual void visit(Builtin &builtin) = 0; 229 | virtual void visit(Call &call) = 0; 230 | virtual void visit(Map &map) = 0; 231 | virtual void visit(Variable &var) = 0; 232 | virtual void visit(Binop &binop) = 0; 233 | virtual void visit(Unop &unop) = 0; 234 | virtual void visit(FieldAccess &acc) = 0; 235 | virtual void visit(Cast &cast) = 0; 236 | virtual void visit(ExprStatement &expr) = 0; 237 | virtual void visit(AssignMapStatement &assignment) = 0; 238 | virtual void visit(AssignVarStatement &assignment) = 0; 239 | virtual void visit(Predicate &pred) = 0; 240 | virtual void visit(AttachPoint &ap) = 0; 241 | virtual void visit(Probe &probe) = 0; 242 | virtual void visit(Include &include) = 0; 243 | virtual void visit(Program &program) = 0; 244 | }; 245 | 246 | std::string opstr(Binop &binop); 247 | std::string opstr(Unop &unop); 248 | 249 | } // namespace ast 250 | } // namespace bpftrace 251 | -------------------------------------------------------------------------------- /src/parser.yy: -------------------------------------------------------------------------------- 1 | %skeleton "lalr1.cc" 2 | %require "3.0.4" 3 | %defines 4 | %define api.namespace { bpftrace } 5 | %define parser_class_name { Parser } 6 | 7 | %define api.token.constructor 8 | %define api.value.type variant 9 | %define parse.assert 10 | 11 | %define parse.error verbose 12 | 13 | %param { bpftrace::Driver &driver } 14 | %param { void *yyscanner } 15 | %locations 16 | 17 | // Forward declarations of classes referenced in the parser 18 | %code requires 19 | { 20 | namespace bpftrace { 21 | class Driver; 22 | namespace ast { 23 | class Node; 24 | } // namespace ast 25 | } // namespace bpftrace 26 | #include "ast.h" 27 | } 28 | 29 | %{ 30 | #include 31 | 32 | #include "driver.h" 33 | 34 | void yyerror(bpftrace::Driver &driver, const char *s); 35 | %} 36 | 37 | %token 38 | END 0 "end of file" 39 | COLON ":" 40 | SEMI ";" 41 | LBRACE "{" 42 | RBRACE "}" 43 | LBRACKET "[" 44 | RBRACKET "]" 45 | LPAREN "(" 46 | RPAREN ")" 47 | ENDPRED "end predicate" 48 | COMMA "," 49 | ASSIGN "=" 50 | EQ "==" 51 | NE "!=" 52 | LE "<=" 53 | GE ">=" 54 | LT "<" 55 | GT ">" 56 | LAND "&&" 57 | LOR "||" 58 | PLUS "+" 59 | MINUS "-" 60 | MUL "*" 61 | DIV "/" 62 | MOD "%" 63 | BAND "&" 64 | BOR "|" 65 | BXOR "^" 66 | LNOT "!" 67 | BNOT "~" 68 | INCLUDE "#include" 69 | DOT "." 70 | PTR "->" 71 | ; 72 | 73 | %token BUILTIN "builtin" 74 | %token IDENT "identifier" 75 | %token PATH "path" 76 | %token HEADER "header" 77 | %token STRING "string" 78 | %token MAP "map" 79 | %token VAR "variable" 80 | %token INT "integer" 81 | 82 | %type includes 83 | %type include 84 | %type probes 85 | %type probe 86 | %type pred 87 | %type block stmts 88 | %type stmt 89 | %type expr 90 | %type call 91 | %type map 92 | %type var 93 | %type vargs 94 | %type attach_points 95 | %type attach_point 96 | %type wildcard 97 | %type type 98 | %type ident 99 | 100 | %right ASSIGN 101 | %left LOR 102 | %left LAND 103 | %left BOR 104 | %left BXOR 105 | %left BAND 106 | %left EQ NE 107 | %left LE GE LT GT 108 | %left PLUS MINUS 109 | %left MUL DIV MOD 110 | %right LNOT BNOT DEREF CAST 111 | %left DOT PTR 112 | 113 | %start program 114 | 115 | %% 116 | 117 | program : includes probes { driver.root_ = new ast::Program($1, $2); } 118 | ; 119 | 120 | includes : includes include { $$ = $1; $1->push_back($2); } 121 | | { $$ = new ast::IncludeList; } 122 | ; 123 | 124 | include : INCLUDE STRING { $$ = new ast::Include($2, false); } 125 | | INCLUDE HEADER { $$ = new ast::Include($2.substr(1, $2.size()-2), true); } 126 | ; 127 | 128 | probes : probes probe { $$ = $1; $1->push_back($2); } 129 | | probe { $$ = new ast::ProbeList; $$->push_back($1); } 130 | ; 131 | 132 | probe : attach_points pred block { $$ = new ast::Probe($1, $2, $3); } 133 | ; 134 | 135 | attach_points : attach_points "," attach_point { $$ = $1; $1->push_back($3); } 136 | | attach_point { $$ = new ast::AttachPointList; $$->push_back($1); } 137 | ; 138 | 139 | attach_point : ident { $$ = new ast::AttachPoint($1); } 140 | | ident ":" wildcard { $$ = new ast::AttachPoint($1, $3); } 141 | | ident PATH wildcard { $$ = new ast::AttachPoint($1, $2.substr(1, $2.size()-2), $3); } 142 | | ident PATH INT { $$ = new ast::AttachPoint($1, $2.substr(1, $2.size()-2), $3); } 143 | ; 144 | 145 | wildcard : wildcard ident { $$ = $1 + $2; } 146 | | wildcard MUL { $$ = $1 + "*"; } 147 | | wildcard LBRACKET { $$ = $1 + "["; } 148 | | wildcard RBRACKET { $$ = $1 + "]"; } 149 | | { $$ = ""; } 150 | ; 151 | 152 | pred : DIV expr ENDPRED { $$ = new ast::Predicate($2); } 153 | | { $$ = nullptr; } 154 | ; 155 | 156 | block : "{" stmts "}" { $$ = $2; } 157 | | "{" stmts ";" "}" { $$ = $2; } 158 | ; 159 | 160 | stmts : stmts ";" stmt { $$ = $1; $1->push_back($3); } 161 | | stmt { $$ = new ast::StatementList; $$->push_back($1); } 162 | ; 163 | 164 | stmt : expr { $$ = new ast::ExprStatement($1); } 165 | | map "=" expr { $$ = new ast::AssignMapStatement($1, $3); } 166 | | var "=" expr { $$ = new ast::AssignVarStatement($1, $3); } 167 | ; 168 | 169 | expr : INT { $$ = new ast::Integer($1); } 170 | | STRING { $$ = new ast::String($1); } 171 | | BUILTIN { $$ = new ast::Builtin($1); } 172 | | map { $$ = $1; } 173 | | var { $$ = $1; } 174 | | call { $$ = $1; } 175 | | "(" expr ")" { $$ = $2; } 176 | | expr EQ expr { $$ = new ast::Binop($1, token::EQ, $3); } 177 | | expr NE expr { $$ = new ast::Binop($1, token::NE, $3); } 178 | | expr LE expr { $$ = new ast::Binop($1, token::LE, $3); } 179 | | expr GE expr { $$ = new ast::Binop($1, token::GE, $3); } 180 | | expr LT expr { $$ = new ast::Binop($1, token::LT, $3); } 181 | | expr GT expr { $$ = new ast::Binop($1, token::GT, $3); } 182 | | expr LAND expr { $$ = new ast::Binop($1, token::LAND, $3); } 183 | | expr LOR expr { $$ = new ast::Binop($1, token::LOR, $3); } 184 | | expr PLUS expr { $$ = new ast::Binop($1, token::PLUS, $3); } 185 | | expr MINUS expr { $$ = new ast::Binop($1, token::MINUS, $3); } 186 | | expr MUL expr { $$ = new ast::Binop($1, token::MUL, $3); } 187 | | expr DIV expr { $$ = new ast::Binop($1, token::DIV, $3); } 188 | | expr MOD expr { $$ = new ast::Binop($1, token::MOD, $3); } 189 | | expr BAND expr { $$ = new ast::Binop($1, token::BAND, $3); } 190 | | expr BOR expr { $$ = new ast::Binop($1, token::BOR, $3); } 191 | | expr BXOR expr { $$ = new ast::Binop($1, token::BXOR, $3); } 192 | | LNOT expr { $$ = new ast::Unop(token::LNOT, $2); } 193 | | BNOT expr { $$ = new ast::Unop(token::BNOT, $2); } 194 | | MUL expr %prec DEREF { $$ = new ast::Unop(token::MUL, $2); } 195 | | expr DOT ident { $$ = new ast::FieldAccess($1, $3); } 196 | | expr PTR ident { $$ = new ast::FieldAccess(new ast::Unop(token::MUL, $1), $3); } 197 | | "(" type ")" expr %prec CAST { $$ = new ast::Cast($2, $4); } 198 | ; 199 | 200 | type : IDENT { $$ = $1; } 201 | | IDENT MUL { $$ = $1 + "*"; } 202 | ; 203 | 204 | ident : IDENT { $$ = $1; } 205 | | BUILTIN { $$ = $1; } 206 | ; 207 | 208 | call : ident "(" ")" { $$ = new ast::Call($1); } 209 | | ident "(" vargs ")" { $$ = new ast::Call($1, $3); } 210 | ; 211 | 212 | map : MAP { $$ = new ast::Map($1); } 213 | | MAP "[" vargs "]" { $$ = new ast::Map($1, $3); } 214 | ; 215 | 216 | var : VAR { $$ = new ast::Variable($1); } 217 | ; 218 | 219 | vargs : vargs "," expr { $$ = $1; $1->push_back($3); } 220 | | expr { $$ = new ast::ExpressionList; $$->push_back($1); } 221 | ; 222 | 223 | %% 224 | 225 | void bpftrace::Parser::error(const location &l, const std::string &m) 226 | { 227 | driver.error(l, m); 228 | } 229 | -------------------------------------------------------------------------------- /src/attached_probe.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "attached_probe.h" 10 | #include "bcc_syms.h" 11 | #include "common.h" 12 | #include "libbpf.h" 13 | #include 14 | #include 15 | 16 | namespace bpftrace { 17 | 18 | bpf_probe_attach_type attachtype(ProbeType t) 19 | { 20 | switch (t) 21 | { 22 | case ProbeType::kprobe: return BPF_PROBE_ENTRY; break; 23 | case ProbeType::kretprobe: return BPF_PROBE_RETURN; break; 24 | case ProbeType::uprobe: return BPF_PROBE_ENTRY; break; 25 | case ProbeType::uretprobe: return BPF_PROBE_RETURN; break; 26 | default: abort(); 27 | } 28 | } 29 | 30 | bpf_prog_type progtype(ProbeType t) 31 | { 32 | switch (t) 33 | { 34 | case ProbeType::kprobe: return BPF_PROG_TYPE_KPROBE; break; 35 | case ProbeType::kretprobe: return BPF_PROG_TYPE_KPROBE; break; 36 | case ProbeType::uprobe: return BPF_PROG_TYPE_KPROBE; break; 37 | case ProbeType::uretprobe: return BPF_PROG_TYPE_KPROBE; break; 38 | case ProbeType::tracepoint: return BPF_PROG_TYPE_TRACEPOINT; break; 39 | case ProbeType::profile: return BPF_PROG_TYPE_PERF_EVENT; break; 40 | default: abort(); 41 | } 42 | } 43 | 44 | 45 | AttachedProbe::AttachedProbe(Probe &probe, std::tuple func) 46 | : probe_(probe), func_(func) 47 | { 48 | load_prog(); 49 | switch (probe_.type) 50 | { 51 | case ProbeType::kprobe: 52 | case ProbeType::kretprobe: 53 | attach_kprobe(); 54 | break; 55 | case ProbeType::uprobe: 56 | case ProbeType::uretprobe: 57 | attach_uprobe(); 58 | break; 59 | case ProbeType::tracepoint: 60 | attach_tracepoint(); 61 | break; 62 | case ProbeType::profile: 63 | attach_profile(); 64 | break; 65 | default: 66 | abort(); 67 | } 68 | } 69 | 70 | AttachedProbe::~AttachedProbe() 71 | { 72 | close(progfd_); 73 | 74 | int err = 0; 75 | for (int perf_event_fd : perf_event_fds_) 76 | { 77 | err = bpf_close_perf_event_fd(perf_event_fd); 78 | if (err) 79 | std::cerr << "Error closing perf event FDs for probe: " << probe_.name << std::endl; 80 | } 81 | 82 | err = 0; 83 | switch (probe_.type) 84 | { 85 | case ProbeType::kprobe: 86 | case ProbeType::kretprobe: 87 | err = bpf_detach_kprobe(eventname().c_str()); 88 | break; 89 | case ProbeType::uprobe: 90 | case ProbeType::uretprobe: 91 | err = bpf_detach_uprobe(eventname().c_str()); 92 | break; 93 | case ProbeType::tracepoint: 94 | err = bpf_detach_tracepoint(probe_.path.c_str(), eventname().c_str()); 95 | break; 96 | case ProbeType::profile: 97 | break; 98 | default: 99 | abort(); 100 | } 101 | if (err) 102 | std::cerr << "Error detaching probe: " << probe_.name << std::endl; 103 | } 104 | 105 | std::string AttachedProbe::eventprefix() const 106 | { 107 | switch (attachtype(probe_.type)) 108 | { 109 | case BPF_PROBE_ENTRY: 110 | return "p_"; 111 | case BPF_PROBE_RETURN: 112 | return "r_"; 113 | default: 114 | abort(); 115 | } 116 | } 117 | 118 | std::string AttachedProbe::eventname() const 119 | { 120 | std::ostringstream offset_str; 121 | switch (probe_.type) 122 | { 123 | case ProbeType::kprobe: 124 | case ProbeType::kretprobe: 125 | return eventprefix() + probe_.attach_point; 126 | case ProbeType::uprobe: 127 | case ProbeType::uretprobe: 128 | offset_str << std::hex << offset(); 129 | return eventprefix() + sanitise(probe_.path) + "_" + offset_str.str(); 130 | case ProbeType::tracepoint: 131 | return probe_.attach_point; 132 | default: 133 | abort(); 134 | } 135 | } 136 | 137 | std::string AttachedProbe::sanitise(const std::string &str) 138 | { 139 | return std::regex_replace(str, std::regex("[^A-Za-z0-9_]"), "_"); 140 | } 141 | 142 | uint64_t AttachedProbe::offset() const 143 | { 144 | bcc_symbol sym; 145 | int err = bcc_resolve_symname(probe_.path.c_str(), probe_.attach_point.c_str(), 146 | 0, 0, nullptr, &sym); 147 | 148 | if (err) 149 | throw std::runtime_error("Could not resolve symbol: " + probe_.path + ":" + probe_.attach_point); 150 | 151 | return sym.offset; 152 | } 153 | 154 | static unsigned kernel_version(int attempt) 155 | { 156 | switch (attempt) 157 | { 158 | case 0: 159 | return LINUX_VERSION_CODE; 160 | case 1: 161 | struct utsname utsname; 162 | uname(&utsname); 163 | unsigned x, y, z; 164 | sscanf(utsname.release, "%d.%d.%d", &x, &y, &z); 165 | return KERNEL_VERSION(x, y, z); 166 | case 2: 167 | // try to get the definition of LINUX_VERSION_CODE at runtime. 168 | // needed if bpftrace is compiled on a different linux version than it's used on. 169 | // e.g. if built with docker. 170 | // the reason case 0 doesn't work for this is because it uses the preprocessor directive, 171 | // which is by definition a compile-time constant 172 | std::ifstream linux_version_header{"/usr/include/linux/version.h"}; 173 | const std::string content{std::istreambuf_iterator(linux_version_header), 174 | std::istreambuf_iterator()}; 175 | const std::regex regex{"#define\\s+LINUX_VERSION_CODE\\s+(\\d+)"}; 176 | std::smatch match; 177 | 178 | if (std::regex_search(content.begin(), content.end(), match, regex)) 179 | return static_cast(std::stoi(match[1])); 180 | 181 | return 0; 182 | } 183 | } 184 | 185 | void AttachedProbe::load_prog() 186 | { 187 | uint8_t *insns = std::get<0>(func_); 188 | int prog_len = std::get<1>(func_); 189 | const char *license = "GPL"; 190 | int log_level = 0; 191 | char *log_buf = nullptr; 192 | unsigned log_buf_size = 0; 193 | 194 | // Redirect stderr, so we don't get error messages from BCC 195 | int old_stderr, new_stderr; 196 | fflush(stderr); 197 | old_stderr = dup(2); 198 | new_stderr = open("/dev/null", O_WRONLY); 199 | dup2(new_stderr, 2); 200 | close(new_stderr); 201 | 202 | for (int attempt=0; attempt<3; attempt++) 203 | { 204 | progfd_ = bpf_prog_load(progtype(probe_.type), probe_.name.c_str(), 205 | reinterpret_cast(insns), prog_len, license, 206 | kernel_version(attempt), log_level, log_buf, log_buf_size); 207 | if (progfd_ >= 0) 208 | break; 209 | } 210 | 211 | // Restore stderr 212 | fflush(stderr); 213 | dup2(old_stderr, 2); 214 | close(old_stderr); 215 | 216 | if (progfd_ < 0) 217 | throw std::runtime_error("Error loading program: " + probe_.name); 218 | } 219 | 220 | void AttachedProbe::attach_kprobe() 221 | { 222 | int perf_event_fd = bpf_attach_kprobe(progfd_, attachtype(probe_.type), 223 | eventname().c_str(), probe_.attach_point.c_str(), 0); 224 | 225 | if (perf_event_fd < 0) 226 | throw std::runtime_error("Error attaching probe: '" + probe_.name + "'"); 227 | 228 | perf_event_fds_.push_back(perf_event_fd); 229 | } 230 | 231 | void AttachedProbe::attach_uprobe() 232 | { 233 | int pid = -1; 234 | 235 | int perf_event_fd = bpf_attach_uprobe(progfd_, attachtype(probe_.type), 236 | eventname().c_str(), probe_.path.c_str(), offset(), pid); 237 | 238 | if (perf_event_fd < 0) 239 | throw std::runtime_error("Error attaching probe: " + probe_.name); 240 | 241 | perf_event_fds_.push_back(perf_event_fd); 242 | } 243 | 244 | void AttachedProbe::attach_tracepoint() 245 | { 246 | int perf_event_fd = bpf_attach_tracepoint(progfd_, probe_.path.c_str(), 247 | eventname().c_str()); 248 | 249 | if (perf_event_fd < 0) 250 | throw std::runtime_error("Error attaching probe: " + probe_.name); 251 | 252 | perf_event_fds_.push_back(perf_event_fd); 253 | } 254 | 255 | void AttachedProbe::attach_profile() 256 | { 257 | int pid = -1; 258 | int group_fd = -1; 259 | 260 | uint64_t period, freq; 261 | if (probe_.path == "hz") 262 | { 263 | period = 0; 264 | freq = probe_.freq; 265 | } 266 | else if (probe_.path == "s") 267 | { 268 | period = probe_.freq * 1e9; 269 | freq = 0; 270 | } 271 | else if (probe_.path == "ms") 272 | { 273 | period = probe_.freq * 1e6; 274 | freq = 0; 275 | } 276 | else if (probe_.path == "us") 277 | { 278 | period = probe_.freq * 1e3; 279 | freq = 0; 280 | } 281 | else 282 | { 283 | abort(); 284 | } 285 | 286 | std::vector cpus = ebpf::get_online_cpus(); 287 | for (int cpu : cpus) 288 | { 289 | int perf_event_fd = bpf_attach_perf_event(progfd_, PERF_TYPE_SOFTWARE, 290 | PERF_COUNT_SW_CPU_CLOCK, period, freq, pid, cpu, group_fd); 291 | 292 | if (perf_event_fd < 0) 293 | throw std::runtime_error("Error attaching probe: " + probe_.name); 294 | 295 | perf_event_fds_.push_back(perf_event_fd); 296 | } 297 | } 298 | 299 | } // namespace bpftrace 300 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/ast/irbuilderbpf.cpp: -------------------------------------------------------------------------------- 1 | #include "irbuilderbpf.h" 2 | #include "libbpf.h" 3 | 4 | #include 5 | 6 | namespace bpftrace { 7 | namespace ast { 8 | 9 | IRBuilderBPF::IRBuilderBPF(LLVMContext &context, 10 | Module &module, 11 | BPFtrace &bpftrace) 12 | : IRBuilder<>(context), 13 | module_(module), 14 | bpftrace_(bpftrace) 15 | { 16 | // Declare external LLVM function 17 | FunctionType *pseudo_func_type = FunctionType::get( 18 | getInt64Ty(), 19 | {getInt64Ty(), getInt64Ty()}, 20 | false); 21 | Function::Create( 22 | pseudo_func_type, 23 | GlobalValue::ExternalLinkage, 24 | "llvm.bpf.pseudo", 25 | &module_); 26 | } 27 | 28 | AllocaInst *IRBuilderBPF::CreateAllocaBPF(llvm::Type *ty, const std::string &name) 29 | { 30 | Function *parent = GetInsertBlock()->getParent(); 31 | BasicBlock &entry_block = parent->getEntryBlock(); 32 | 33 | auto ip = saveIP(); 34 | if (entry_block.empty()) 35 | SetInsertPoint(&entry_block); 36 | else 37 | SetInsertPoint(&entry_block.front()); 38 | AllocaInst *alloca = CreateAlloca(ty, nullptr, name); // TODO dodgy 39 | restoreIP(ip); 40 | 41 | CreateLifetimeStart(alloca); 42 | return alloca; 43 | } 44 | 45 | AllocaInst *IRBuilderBPF::CreateAllocaBPF(const SizedType &stype, const std::string &name) 46 | { 47 | llvm::Type *ty = GetType(stype); 48 | return CreateAllocaBPF(ty, name); 49 | } 50 | 51 | AllocaInst *IRBuilderBPF::CreateAllocaMapKey(int bytes, const std::string &name) 52 | { 53 | llvm::Type *ty = ArrayType::get(getInt8Ty(), bytes); 54 | return CreateAllocaBPF(ty, name); 55 | } 56 | 57 | llvm::Type *IRBuilderBPF::GetType(const SizedType &stype) 58 | { 59 | llvm::Type *ty; 60 | if (stype.type == Type::string || stype.type == Type::cast) 61 | { 62 | ty = ArrayType::get(getInt8Ty(), stype.size); 63 | } 64 | else 65 | { 66 | switch (stype.size) 67 | { 68 | case 8: 69 | ty = getInt64Ty(); 70 | break; 71 | case 4: 72 | ty = getInt32Ty(); 73 | break; 74 | default: 75 | abort(); 76 | } 77 | } 78 | return ty; 79 | } 80 | 81 | CallInst *IRBuilderBPF::CreateBpfPseudoCall(int mapfd) 82 | { 83 | Function *pseudo_func = module_.getFunction("llvm.bpf.pseudo"); 84 | return CreateCall(pseudo_func, {getInt64(BPF_PSEUDO_MAP_FD), getInt64(mapfd)}, "pseudo"); 85 | } 86 | 87 | CallInst *IRBuilderBPF::CreateBpfPseudoCall(Map &map) 88 | { 89 | int mapfd = bpftrace_.maps_[map.ident]->mapfd_; 90 | return CreateBpfPseudoCall(mapfd); 91 | } 92 | 93 | Value *IRBuilderBPF::CreateMapLookupElem(Map &map, AllocaInst *key) 94 | { 95 | Value *map_ptr = CreateBpfPseudoCall(map); 96 | 97 | // void *map_lookup_elem(&map, &key) 98 | // Return: Map value or NULL 99 | FunctionType *lookup_func_type = FunctionType::get( 100 | getInt8PtrTy(), 101 | {getInt8PtrTy(), getInt8PtrTy()}, 102 | false); 103 | PointerType *lookup_func_ptr_type = PointerType::get(lookup_func_type, 0); 104 | Constant *lookup_func = ConstantExpr::getCast( 105 | Instruction::IntToPtr, 106 | getInt64(BPF_FUNC_map_lookup_elem), 107 | lookup_func_ptr_type); 108 | CallInst *call = CreateCall(lookup_func, {map_ptr, key}, "lookup_elem"); 109 | 110 | // Check if result == 0 111 | Function *parent = GetInsertBlock()->getParent(); 112 | BasicBlock *lookup_success_block = BasicBlock::Create(module_.getContext(), "lookup_success", parent); 113 | BasicBlock *lookup_failure_block = BasicBlock::Create(module_.getContext(), "lookup_failure", parent); 114 | BasicBlock *lookup_merge_block = BasicBlock::Create(module_.getContext(), "lookup_merge", parent); 115 | 116 | AllocaInst *value = CreateAllocaBPF(map.type, "lookup_elem_val"); 117 | Value *condition = CreateICmpNE( 118 | CreateIntCast(call, getInt8PtrTy(), true), 119 | ConstantExpr::getCast(Instruction::IntToPtr, getInt64(0), getInt8PtrTy()), 120 | "map_lookup_cond"); 121 | CreateCondBr(condition, lookup_success_block, lookup_failure_block); 122 | 123 | SetInsertPoint(lookup_success_block); 124 | if (map.type.type == Type::string) 125 | CreateMemCpy(value, call, map.type.size, 1); 126 | else 127 | CreateStore(CreateLoad(getInt64Ty(), call), value); 128 | CreateBr(lookup_merge_block); 129 | 130 | SetInsertPoint(lookup_failure_block); 131 | if (map.type.type == Type::string) 132 | CreateMemSet(value, getInt8(0), map.type.size, 1); 133 | else 134 | CreateStore(getInt64(0), value); 135 | CreateBr(lookup_merge_block); 136 | 137 | SetInsertPoint(lookup_merge_block); 138 | if (map.type.type == Type::string) 139 | return value; 140 | return CreateLoad(value); 141 | } 142 | 143 | void IRBuilderBPF::CreateMapUpdateElem(Map &map, AllocaInst *key, Value *val) 144 | { 145 | Value *map_ptr = CreateBpfPseudoCall(map); 146 | Value *flags = getInt64(0); 147 | 148 | // int map_update_elem(&map, &key, &value, flags) 149 | // Return: 0 on success or negative error 150 | FunctionType *update_func_type = FunctionType::get( 151 | getInt64Ty(), 152 | {getInt8PtrTy(), getInt8PtrTy(), getInt8PtrTy(), getInt64Ty()}, 153 | false); 154 | PointerType *update_func_ptr_type = PointerType::get(update_func_type, 0); 155 | Constant *update_func = ConstantExpr::getCast( 156 | Instruction::IntToPtr, 157 | getInt64(BPF_FUNC_map_update_elem), 158 | update_func_ptr_type); 159 | CallInst *call = CreateCall(update_func, {map_ptr, key, val, flags}, "update_elem"); 160 | } 161 | 162 | void IRBuilderBPF::CreateMapDeleteElem(Map &map, AllocaInst *key) 163 | { 164 | Value *map_ptr = CreateBpfPseudoCall(map); 165 | Value *flags = getInt64(0); 166 | 167 | // int map_delete_elem(&map, &key) 168 | // Return: 0 on success or negative error 169 | FunctionType *delete_func_type = FunctionType::get( 170 | getInt64Ty(), 171 | {getInt8PtrTy(), getInt8PtrTy()}, 172 | false); 173 | PointerType *delete_func_ptr_type = PointerType::get(delete_func_type, 0); 174 | Constant *delete_func = ConstantExpr::getCast( 175 | Instruction::IntToPtr, 176 | getInt64(BPF_FUNC_map_delete_elem), 177 | delete_func_ptr_type); 178 | CallInst *call = CreateCall(delete_func, {map_ptr, key}, "delete_elem"); 179 | } 180 | 181 | void IRBuilderBPF::CreateProbeRead(AllocaInst *dst, size_t size, Value *src) 182 | { 183 | // int bpf_probe_read(void *dst, int size, void *src) 184 | // Return: 0 on success or negative error 185 | FunctionType *proberead_func_type = FunctionType::get( 186 | getInt64Ty(), 187 | {getInt8PtrTy(), getInt64Ty(), getInt8PtrTy()}, 188 | false); 189 | PointerType *proberead_func_ptr_type = PointerType::get(proberead_func_type, 0); 190 | Constant *proberead_func = ConstantExpr::getCast( 191 | Instruction::IntToPtr, 192 | getInt64(BPF_FUNC_probe_read), 193 | proberead_func_ptr_type); 194 | CallInst *call = CreateCall(proberead_func, {dst, getInt64(size), src}, "probe_read"); 195 | } 196 | 197 | void IRBuilderBPF::CreateProbeReadStr(AllocaInst *dst, size_t size, Value *src) 198 | { 199 | // int bpf_probe_read_str(void *dst, int size, const void *unsafe_ptr) 200 | FunctionType *probereadstr_func_type = FunctionType::get( 201 | getInt64Ty(), 202 | {getInt8PtrTy(), getInt64Ty(), getInt8PtrTy()}, 203 | false); 204 | PointerType *probereadstr_func_ptr_type = PointerType::get(probereadstr_func_type, 0); 205 | Constant *probereadstr_func = ConstantExpr::getCast( 206 | Instruction::IntToPtr, 207 | getInt64(BPF_FUNC_probe_read_str), 208 | probereadstr_func_ptr_type); 209 | CallInst *call = CreateCall(probereadstr_func, {dst, getInt64(size), src}, "probe_read_str"); 210 | } 211 | 212 | CallInst *IRBuilderBPF::CreateGetNs() 213 | { 214 | // u64 ktime_get_ns() 215 | // Return: current ktime 216 | FunctionType *gettime_func_type = FunctionType::get(getInt64Ty(), false); 217 | PointerType *gettime_func_ptr_type = PointerType::get(gettime_func_type, 0); 218 | Constant *gettime_func = ConstantExpr::getCast( 219 | Instruction::IntToPtr, 220 | getInt64(BPF_FUNC_ktime_get_ns), 221 | gettime_func_ptr_type); 222 | return CreateCall(gettime_func, {}, "get_ns"); 223 | } 224 | 225 | CallInst *IRBuilderBPF::CreateGetPidTgid() 226 | { 227 | // u64 bpf_get_current_pid_tgid(void) 228 | // Return: current->tgid << 32 | current->pid 229 | FunctionType *getpidtgid_func_type = FunctionType::get(getInt64Ty(), false); 230 | PointerType *getpidtgid_func_ptr_type = PointerType::get(getpidtgid_func_type, 0); 231 | Constant *getpidtgid_func = ConstantExpr::getCast( 232 | Instruction::IntToPtr, 233 | getInt64(BPF_FUNC_get_current_pid_tgid), 234 | getpidtgid_func_ptr_type); 235 | return CreateCall(getpidtgid_func, {}, "get_pid_tgid"); 236 | } 237 | 238 | CallInst *IRBuilderBPF::CreateGetUidGid() 239 | { 240 | // u64 bpf_get_current_uid_gid(void) 241 | // Return: current_gid << 32 | current_uid 242 | FunctionType *getuidgid_func_type = FunctionType::get(getInt64Ty(), false); 243 | PointerType *getuidgid_func_ptr_type = PointerType::get(getuidgid_func_type, 0); 244 | Constant *getuidgid_func = ConstantExpr::getCast( 245 | Instruction::IntToPtr, 246 | getInt64(BPF_FUNC_get_current_uid_gid), 247 | getuidgid_func_ptr_type); 248 | return CreateCall(getuidgid_func, {}, "get_uid_gid"); 249 | } 250 | 251 | CallInst *IRBuilderBPF::CreateGetCpuId() 252 | { 253 | // u32 bpf_raw_smp_processor_id(void) 254 | // Return: SMP processor ID 255 | FunctionType *getcpuid_func_type = FunctionType::get(getInt64Ty(), false); 256 | PointerType *getcpuid_func_ptr_type = PointerType::get(getcpuid_func_type, 0); 257 | Constant *getcpuid_func = ConstantExpr::getCast( 258 | Instruction::IntToPtr, 259 | getInt64(BPF_FUNC_get_smp_processor_id), 260 | getcpuid_func_ptr_type); 261 | return CreateCall(getcpuid_func, {}, "get_cpu_id"); 262 | } 263 | 264 | CallInst *IRBuilderBPF::CreateGetStackId(Value *ctx, bool ustack) 265 | { 266 | Value *map_ptr = CreateBpfPseudoCall(bpftrace_.stackid_map_->mapfd_); 267 | 268 | int flags = 0; 269 | if (ustack) 270 | flags |= (1<<8); 271 | Value *flags_val = getInt64(flags); 272 | 273 | // int bpf_get_stackid(ctx, map, flags) 274 | // Return: >= 0 stackid on success or negative error 275 | FunctionType *getstackid_func_type = FunctionType::get( 276 | getInt64Ty(), 277 | {getInt8PtrTy(), getInt8PtrTy(), getInt64Ty()}, 278 | false); 279 | PointerType *getstackid_func_ptr_type = PointerType::get(getstackid_func_type, 0); 280 | Constant *getstackid_func = ConstantExpr::getCast( 281 | Instruction::IntToPtr, 282 | getInt64(BPF_FUNC_get_stackid), 283 | getstackid_func_ptr_type); 284 | return CreateCall(getstackid_func, {ctx, map_ptr, flags_val}, "get_stackid"); 285 | } 286 | 287 | void IRBuilderBPF::CreateGetCurrentComm(AllocaInst *buf, size_t size) 288 | { 289 | // int bpf_get_current_comm(char *buf, int size_of_buf) 290 | // Return: 0 on success or negative error 291 | FunctionType *getcomm_func_type = FunctionType::get( 292 | getInt64Ty(), 293 | {getInt8PtrTy(), getInt64Ty()}, 294 | false); 295 | PointerType *getcomm_func_ptr_type = PointerType::get(getcomm_func_type, 0); 296 | Constant *getcomm_func = ConstantExpr::getCast( 297 | Instruction::IntToPtr, 298 | getInt64(BPF_FUNC_get_current_comm), 299 | getcomm_func_ptr_type); 300 | CreateCall(getcomm_func, {buf, getInt64(size)}, "get_comm"); 301 | } 302 | 303 | void IRBuilderBPF::CreatePerfEventOutput(Value *ctx, Value *data, size_t size) 304 | { 305 | Value *map_ptr = CreateBpfPseudoCall(bpftrace_.perf_event_map_->mapfd_); 306 | 307 | Value *flags_val = CreateGetCpuId(); 308 | Value *size_val = getInt64(size); 309 | 310 | // int bpf_perf_event_output(ctx, map, flags, data, size) 311 | FunctionType *perfoutput_func_type = FunctionType::get( 312 | getInt64Ty(), 313 | {getInt8PtrTy(), getInt8PtrTy(), getInt64Ty(), getInt8PtrTy(), getInt64Ty()}, 314 | false); 315 | PointerType *perfoutput_func_ptr_type = PointerType::get(perfoutput_func_type, 0); 316 | Constant *perfoutput_func = ConstantExpr::getCast( 317 | Instruction::IntToPtr, 318 | getInt64(BPF_FUNC_perf_event_output), 319 | perfoutput_func_ptr_type); 320 | CreateCall(perfoutput_func, {ctx, map_ptr, flags_val, data, size_val}, "perf_event_output"); 321 | } 322 | 323 | } // namespace ast 324 | } // namespace bpftrace 325 | -------------------------------------------------------------------------------- /tests/parser.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "gtest/gtest.h" 4 | #include "driver.h" 5 | #include "printer.h" 6 | 7 | namespace bpftrace { 8 | namespace test { 9 | namespace parser { 10 | 11 | using Printer = ast::Printer; 12 | 13 | void test(const std::string &input, const std::string &output) 14 | { 15 | Driver driver; 16 | ASSERT_EQ(driver.parse_str(input), 0); 17 | 18 | std::ostringstream out; 19 | Printer printer(out); 20 | driver.root_->accept(printer); 21 | EXPECT_EQ(output, out.str()); 22 | } 23 | 24 | TEST(Parser, builtin_variables) 25 | { 26 | test("kprobe:f { pid }", "Program\n kprobe:f\n builtin: pid\n"); 27 | test("kprobe:f { tid }", "Program\n kprobe:f\n builtin: tid\n"); 28 | test("kprobe:f { uid }", "Program\n kprobe:f\n builtin: uid\n"); 29 | test("kprobe:f { gid }", "Program\n kprobe:f\n builtin: gid\n"); 30 | test("kprobe:f { nsecs }", "Program\n kprobe:f\n builtin: nsecs\n"); 31 | test("kprobe:f { cpu }", "Program\n kprobe:f\n builtin: cpu\n"); 32 | test("kprobe:f { comm }", "Program\n kprobe:f\n builtin: comm\n"); 33 | test("kprobe:f { stack }", "Program\n kprobe:f\n builtin: stack\n"); 34 | test("kprobe:f { ustack }", "Program\n kprobe:f\n builtin: ustack\n"); 35 | test("kprobe:f { arg0 }", "Program\n kprobe:f\n builtin: arg0\n"); 36 | test("kprobe:f { retval }", "Program\n kprobe:f\n builtin: retval\n"); 37 | test("kprobe:f { func }", "Program\n kprobe:f\n builtin: func\n"); 38 | } 39 | 40 | TEST(Parser, map_assign) 41 | { 42 | test("kprobe:sys_open { @x = 1; }", 43 | "Program\n" 44 | " kprobe:sys_open\n" 45 | " =\n" 46 | " map: @x\n" 47 | " int: 1\n"); 48 | test("kprobe:sys_open { @x = @y; }", 49 | "Program\n" 50 | " kprobe:sys_open\n" 51 | " =\n" 52 | " map: @x\n" 53 | " map: @y\n"); 54 | test("kprobe:sys_open { @x = arg0; }", 55 | "Program\n" 56 | " kprobe:sys_open\n" 57 | " =\n" 58 | " map: @x\n" 59 | " builtin: arg0\n"); 60 | test("kprobe:sys_open { @x = count(); }", 61 | "Program\n" 62 | " kprobe:sys_open\n" 63 | " =\n" 64 | " map: @x\n" 65 | " call: count\n"); 66 | test("kprobe:sys_open { @x = \"mystring\" }", 67 | "Program\n" 68 | " kprobe:sys_open\n" 69 | " =\n" 70 | " map: @x\n" 71 | " string: mystring\n"); 72 | test("kprobe:sys_open { @x = $myvar; }", 73 | "Program\n" 74 | " kprobe:sys_open\n" 75 | " =\n" 76 | " map: @x\n" 77 | " variable: $myvar\n"); 78 | } 79 | 80 | TEST(Parser, variable_assign) 81 | { 82 | test("kprobe:sys_open { $x = 1; }", 83 | "Program\n" 84 | " kprobe:sys_open\n" 85 | " =\n" 86 | " variable: $x\n" 87 | " int: 1\n"); 88 | } 89 | 90 | TEST(Parser, map_key) 91 | { 92 | test("kprobe:sys_open { @x[0] = 1; @x[0,1,2] = 1; }", 93 | "Program\n" 94 | " kprobe:sys_open\n" 95 | " =\n" 96 | " map: @x\n" 97 | " int: 0\n" 98 | " int: 1\n" 99 | " =\n" 100 | " map: @x\n" 101 | " int: 0\n" 102 | " int: 1\n" 103 | " int: 2\n" 104 | " int: 1\n"); 105 | 106 | test("kprobe:sys_open { @x[@a] = 1; @x[@a,@b,@c] = 1; }", 107 | "Program\n" 108 | " kprobe:sys_open\n" 109 | " =\n" 110 | " map: @x\n" 111 | " map: @a\n" 112 | " int: 1\n" 113 | " =\n" 114 | " map: @x\n" 115 | " map: @a\n" 116 | " map: @b\n" 117 | " map: @c\n" 118 | " int: 1\n"); 119 | 120 | test("kprobe:sys_open { @x[pid] = 1; @x[tid,uid,arg9] = 1; }", 121 | "Program\n" 122 | " kprobe:sys_open\n" 123 | " =\n" 124 | " map: @x\n" 125 | " builtin: pid\n" 126 | " int: 1\n" 127 | " =\n" 128 | " map: @x\n" 129 | " builtin: tid\n" 130 | " builtin: uid\n" 131 | " builtin: arg9\n" 132 | " int: 1\n"); 133 | } 134 | 135 | TEST(Parser, predicate) 136 | { 137 | test("kprobe:sys_open / @x / { 1; }", 138 | "Program\n" 139 | " kprobe:sys_open\n" 140 | " pred\n" 141 | " map: @x\n" 142 | " int: 1\n"); 143 | } 144 | 145 | TEST(Parser, predicate_containing_division) 146 | { 147 | test("kprobe:sys_open /100/25/ { 1; }", 148 | "Program\n" 149 | " kprobe:sys_open\n" 150 | " pred\n" 151 | " /\n" 152 | " int: 100\n" 153 | " int: 25\n" 154 | " int: 1\n"); 155 | } 156 | 157 | TEST(Parser, expressions) 158 | { 159 | test("kprobe:sys_open / 1 <= 2 && (9 - 4 != 5*10 || ~0) || comm == \"string\" /\n" 160 | "{\n" 161 | " 1;\n" 162 | "}", 163 | "Program\n" 164 | " kprobe:sys_open\n" 165 | " pred\n" 166 | " ||\n" 167 | " &&\n" 168 | " <=\n" 169 | " int: 1\n" 170 | " int: 2\n" 171 | " ||\n" 172 | " !=\n" 173 | " -\n" 174 | " int: 9\n" 175 | " int: 4\n" 176 | " *\n" 177 | " int: 5\n" 178 | " int: 10\n" 179 | " ~\n" 180 | " int: 0\n" 181 | " ==\n" 182 | " builtin: comm\n" 183 | " string: string\n" 184 | " int: 1\n"); 185 | } 186 | 187 | TEST(Parser, call) 188 | { 189 | test("kprobe:sys_open { @x = count(); @y = quantize(1,2,3); delete(@x); }", 190 | "Program\n" 191 | " kprobe:sys_open\n" 192 | " =\n" 193 | " map: @x\n" 194 | " call: count\n" 195 | " =\n" 196 | " map: @y\n" 197 | " call: quantize\n" 198 | " int: 1\n" 199 | " int: 2\n" 200 | " int: 3\n" 201 | " call: delete\n" 202 | " map: @x\n"); 203 | } 204 | 205 | TEST(Parser, call_unknown_function) 206 | { 207 | test("kprobe:sys_open { myfunc() }", 208 | "Program\n" 209 | " kprobe:sys_open\n" 210 | " call: myfunc\n"); 211 | } 212 | 213 | TEST(Parser, multiple_probes) 214 | { 215 | test("kprobe:sys_open { 1; } kretprobe:sys_open { 2; }", 216 | "Program\n" 217 | " kprobe:sys_open\n" 218 | " int: 1\n" 219 | " kretprobe:sys_open\n" 220 | " int: 2\n"); 221 | } 222 | 223 | TEST(Parser, uprobe) 224 | { 225 | test("uprobe:/my/program:func { 1; }", 226 | "Program\n" 227 | " uprobe:/my/program:func\n" 228 | " int: 1\n"); 229 | } 230 | 231 | TEST(Parser, escape_chars) 232 | { 233 | test("kprobe:sys_open { \"newline\\nand tab\\tbackslash\\\\quote\\\"here\" }", 234 | "Program\n" 235 | " kprobe:sys_open\n" 236 | " string: newline\\nand tab\\tbackslash\\\\quote\\\"here\n"); 237 | } 238 | 239 | TEST(Parser, begin_probe) 240 | { 241 | test("BEGIN { 1 }", 242 | "Program\n" 243 | " BEGIN\n" 244 | " int: 1\n"); 245 | } 246 | 247 | TEST(Parser, tracepoint_probe) 248 | { 249 | test("tracepoint:sched:sched_switch { 1 }", 250 | "Program\n" 251 | " tracepoint:sched:sched_switch\n" 252 | " int: 1\n"); 253 | } 254 | 255 | TEST(Parser, profile_probe) 256 | { 257 | test("profile:ms:997 { 1 }", 258 | "Program\n" 259 | " profile:ms:997\n" 260 | " int: 1\n"); 261 | } 262 | 263 | TEST(Parser, multiple_attach_points_kprobe) 264 | { 265 | test("BEGIN,kprobe:sys_open,uprobe:/bin/sh:foo,tracepoint:syscalls:sys_enter_* { 1 }", 266 | "Program\n" 267 | " BEGIN\n" 268 | " kprobe:sys_open\n" 269 | " uprobe:/bin/sh:foo\n" 270 | " tracepoint:syscalls:sys_enter_*\n" 271 | " int: 1\n"); 272 | } 273 | 274 | TEST(Parser, character_class_attach_point) 275 | { 276 | test("kprobe:[Ss]y[Ss]_read { 1 }", 277 | "Program\n" 278 | " kprobe:[Ss]y[Ss]_read\n" 279 | " int: 1\n"); 280 | } 281 | 282 | TEST(Parser, wildcard_attach_points) 283 | { 284 | test("kprobe:sys_* { 1 }", 285 | "Program\n" 286 | " kprobe:sys_*\n" 287 | " int: 1\n"); 288 | test("kprobe:*blah { 1 }", 289 | "Program\n" 290 | " kprobe:*blah\n" 291 | " int: 1\n"); 292 | test("kprobe:sys*blah { 1 }", 293 | "Program\n" 294 | " kprobe:sys*blah\n" 295 | " int: 1\n"); 296 | test("kprobe:* { 1 }", 297 | "Program\n" 298 | " kprobe:*\n" 299 | " int: 1\n"); 300 | test("kprobe:sys_* { @x = cpu*retval }", 301 | "Program\n" 302 | " kprobe:sys_*\n" 303 | " =\n" 304 | " map: @x\n" 305 | " *\n" 306 | " builtin: cpu\n" 307 | " builtin: retval\n"); 308 | test("kprobe:sys_* { @x = *arg0 }", 309 | "Program\n" 310 | " kprobe:sys_*\n" 311 | " =\n" 312 | " map: @x\n" 313 | " dereference\n" 314 | " builtin: arg0\n"); 315 | } 316 | 317 | TEST(Parser, short_map_name) 318 | { 319 | test("kprobe:sys_read { @ = 1 }", 320 | "Program\n" 321 | " kprobe:sys_read\n" 322 | " =\n" 323 | " map: @\n" 324 | " int: 1\n"); 325 | } 326 | 327 | TEST(Parser, include) 328 | { 329 | test("#include kprobe:sys_read { @x = 1 }", 330 | "Program\n" 331 | " #include \n" 332 | " kprobe:sys_read\n" 333 | " =\n" 334 | " map: @x\n" 335 | " int: 1\n"); 336 | } 337 | 338 | TEST(Parser, include_quote) 339 | { 340 | test("#include \"stdio.h\" kprobe:sys_read { @x = 1 }", 341 | "Program\n" 342 | " #include \"stdio.h\"\n" 343 | " kprobe:sys_read\n" 344 | " =\n" 345 | " map: @x\n" 346 | " int: 1\n"); 347 | } 348 | 349 | TEST(Parser, include_multiple) 350 | { 351 | test("#include #include \"blah\" #include kprobe:sys_read { @x = 1 }", 352 | "Program\n" 353 | " #include \n" 354 | " #include \"blah\"\n" 355 | " #include \n" 356 | " kprobe:sys_read\n" 357 | " =\n" 358 | " map: @x\n" 359 | " int: 1\n"); 360 | } 361 | 362 | TEST(Parser, brackets) 363 | { 364 | test("kprobe:sys_read { (arg0*arg1) }", 365 | "Program\n" 366 | " kprobe:sys_read\n" 367 | " *\n" 368 | " builtin: arg0\n" 369 | " builtin: arg1\n"); 370 | } 371 | 372 | TEST(Parser, cast) 373 | { 374 | test("kprobe:sys_read { (mytype)arg0; }", 375 | "Program\n" 376 | " kprobe:sys_read\n" 377 | " (mytype)\n" 378 | " builtin: arg0\n"); 379 | } 380 | 381 | TEST(Parser, cast_ptr) 382 | { 383 | test("kprobe:sys_read { (mytype*)arg0; }", 384 | "Program\n" 385 | " kprobe:sys_read\n" 386 | " (mytype*)\n" 387 | " builtin: arg0\n"); 388 | } 389 | 390 | TEST(Parser, cast_or_expr1) 391 | { 392 | test("kprobe:sys_read { (mytype)*arg0; }", 393 | "Program\n" 394 | " kprobe:sys_read\n" 395 | " (mytype)\n" 396 | " dereference\n" 397 | " builtin: arg0\n"); 398 | } 399 | 400 | TEST(Parser, cast_or_expr2) 401 | { 402 | test("kprobe:sys_read { (arg1)*arg0; }", 403 | "Program\n" 404 | " kprobe:sys_read\n" 405 | " *\n" 406 | " builtin: arg1\n" 407 | " builtin: arg0\n"); 408 | } 409 | 410 | TEST(Parser, cast_precedence) 411 | { 412 | test("kprobe:sys_read { (mytype)arg0.field; }", 413 | "Program\n" 414 | " kprobe:sys_read\n" 415 | " (mytype)\n" 416 | " .\n" 417 | " builtin: arg0\n" 418 | " field\n"); 419 | 420 | test("kprobe:sys_read { (mytype*)arg0->field; }", 421 | "Program\n" 422 | " kprobe:sys_read\n" 423 | " (mytype*)\n" 424 | " .\n" 425 | " dereference\n" 426 | " builtin: arg0\n" 427 | " field\n"); 428 | 429 | test("kprobe:sys_read { (mytype)arg0+123; }", 430 | "Program\n" 431 | " kprobe:sys_read\n" 432 | " +\n" 433 | " (mytype)\n" 434 | " builtin: arg0\n" 435 | " int: 123\n"); 436 | } 437 | 438 | TEST(Parser, dereference_precedence) 439 | { 440 | test("kprobe:sys_read { *@x+1 }", 441 | "Program\n" 442 | " kprobe:sys_read\n" 443 | " +\n" 444 | " dereference\n" 445 | " map: @x\n" 446 | " int: 1\n"); 447 | 448 | test("kprobe:sys_read { *@x**@y }", 449 | "Program\n" 450 | " kprobe:sys_read\n" 451 | " *\n" 452 | " dereference\n" 453 | " map: @x\n" 454 | " dereference\n" 455 | " map: @y\n"); 456 | 457 | test("kprobe:sys_read { *@x*@y }", 458 | "Program\n" 459 | " kprobe:sys_read\n" 460 | " *\n" 461 | " dereference\n" 462 | " map: @x\n" 463 | " map: @y\n"); 464 | 465 | test("kprobe:sys_read { *@x.myfield }", 466 | "Program\n" 467 | " kprobe:sys_read\n" 468 | " dereference\n" 469 | " .\n" 470 | " map: @x\n" 471 | " myfield\n"); 472 | } 473 | 474 | TEST(Parser, field_access) 475 | { 476 | test("kprobe:sys_read { @x.myfield; }", 477 | "Program\n" 478 | " kprobe:sys_read\n" 479 | " .\n" 480 | " map: @x\n" 481 | " myfield\n"); 482 | 483 | test("kprobe:sys_read { @x->myfield; }", 484 | "Program\n" 485 | " kprobe:sys_read\n" 486 | " .\n" 487 | " dereference\n" 488 | " map: @x\n" 489 | " myfield\n"); 490 | } 491 | 492 | TEST(Parser, field_access_builtin) 493 | { 494 | test("kprobe:sys_read { @x.count; }", 495 | "Program\n" 496 | " kprobe:sys_read\n" 497 | " .\n" 498 | " map: @x\n" 499 | " count\n"); 500 | 501 | test("kprobe:sys_read { @x->count; }", 502 | "Program\n" 503 | " kprobe:sys_read\n" 504 | " .\n" 505 | " dereference\n" 506 | " map: @x\n" 507 | " count\n"); 508 | } 509 | 510 | } // namespace parser 511 | } // namespace test 512 | } // namespace bpftrace 513 | -------------------------------------------------------------------------------- /tests/semantic_analyser.cpp: -------------------------------------------------------------------------------- 1 | #include "gmock/gmock.h" 2 | #include "gtest/gtest.h" 3 | #include "bpftrace.h" 4 | #include "driver.h" 5 | #include "semantic_analyser.h" 6 | 7 | namespace bpftrace { 8 | namespace test { 9 | namespace semantic_analyser { 10 | 11 | class MockBPFtrace : public BPFtrace { 12 | public: 13 | MOCK_METHOD1(add_probe, int(ast::Probe &p)); 14 | }; 15 | 16 | using ::testing::_; 17 | 18 | void test(BPFtrace &bpftrace, Driver &driver, const std::string &input, int expected_result=0) 19 | { 20 | ASSERT_EQ(driver.parse_str(input), 0); 21 | 22 | std::stringstream out; 23 | ast::SemanticAnalyser semantics(driver.root_, bpftrace, out); 24 | std::stringstream msg; 25 | msg << "\nInput:\n" << input << "\n\nOutput:\n"; 26 | EXPECT_EQ(expected_result, semantics.analyse()) << msg.str() + out.str(); 27 | } 28 | 29 | void test(BPFtrace &bpftrace, const std::string &input, int expected_result=0) 30 | { 31 | Driver driver; 32 | test(bpftrace, driver, input, expected_result); 33 | } 34 | 35 | void test(Driver &driver, const std::string &input, int expected_result=0) 36 | { 37 | BPFtrace bpftrace; 38 | test(bpftrace, driver, input, expected_result); 39 | } 40 | 41 | void test(const std::string &input, int expected_result=0) 42 | { 43 | Field field = { SizedType(Type::integer, 8), 0 }; 44 | Field mystr = { SizedType(Type::string, 8), 8 }; 45 | Field type2_field_ptr = { SizedType(Type::cast, 8, "type2*"), 16 }; 46 | Field type2_field = { SizedType(Type::cast, 8, "type2"), 24 }; 47 | 48 | Struct type1 = { 16, {{"field", field}, 49 | {"mystr", mystr}, 50 | {"type2ptr", type2_field_ptr}, 51 | {"type2", type2_field}} }; 52 | Struct type2 = { 8, {{"field", field}} }; 53 | 54 | BPFtrace bpftrace; 55 | bpftrace.structs_["type1"] = type1; 56 | bpftrace.structs_["type2"] = type2; 57 | 58 | Driver driver; 59 | test(bpftrace, driver, input, expected_result); 60 | } 61 | 62 | TEST(semantic_analyser, builtin_variables) 63 | { 64 | test("kprobe:f { pid }", 0); 65 | test("kprobe:f { tid }", 0); 66 | test("kprobe:f { uid }", 0); 67 | test("kprobe:f { gid }", 0); 68 | test("kprobe:f { nsecs }", 0); 69 | test("kprobe:f { cpu }", 0); 70 | test("kprobe:f { comm }", 0); 71 | test("kprobe:f { stack }", 0); 72 | test("kprobe:f { ustack }", 0); 73 | test("kprobe:f { arg0 }", 0); 74 | test("kprobe:f { retval }", 0); 75 | test("kprobe:f { func }", 0); 76 | // test("kprobe:f { fake }", 1); 77 | } 78 | 79 | TEST(semantic_analyser, builtin_functions) 80 | { 81 | test("kprobe:f { @x = quantize(123) }", 0); 82 | test("kprobe:f { @x = count() }", 0); 83 | test("kprobe:f { @x = 1; delete(@x) }", 0); 84 | test("kprobe:f { str(0xffff) }", 0); 85 | test("kprobe:f { printf(\"hello\\n\") }", 0); 86 | test("kprobe:f { sym(0xffff) }", 0); 87 | test("kprobe:f { usym(0xffff) }", 0); 88 | test("kprobe:f { reg(\"ip\") }", 0); 89 | test("kprobe:f { fake() }", 1); 90 | } 91 | 92 | TEST(semantic_analyser, probe_count) 93 | { 94 | MockBPFtrace bpftrace; 95 | EXPECT_CALL(bpftrace, add_probe(_)).Times(2); 96 | 97 | test(bpftrace, "kprobe:f { 1; } kprobe:d { 1; }"); 98 | } 99 | 100 | TEST(semantic_analyser, undefined_map) 101 | { 102 | test("kprobe:f / @mymap == 123 / { @mymap = 0 }", 0); 103 | test("kprobe:f / @mymap == 123 / { 456; }", 10); 104 | test("kprobe:f / @mymap1 == 1234 / { 1234; @mymap1 = @mymap2; }", 10); 105 | } 106 | 107 | TEST(semantic_analyser, predicate_expressions) 108 | { 109 | test("kprobe:f / 999 / { 123 }", 0); 110 | test("kprobe:f / \"str\" / { 123 }", 10); 111 | test("kprobe:f / stack / { 123 }", 10); 112 | test("kprobe:f / @mymap / { @mymap = \"str\" }", 10); 113 | } 114 | 115 | TEST(semantic_analyser, mismatched_call_types) 116 | { 117 | test("kprobe:f { @x = 1; @x = count(); }", 1); 118 | test("kprobe:f { @x = 1; @x = quantize(0); }", 1); 119 | } 120 | 121 | TEST(semantic_analyser, call_quantize) 122 | { 123 | test("kprobe:f { @x = quantize(1); }", 0); 124 | test("kprobe:f { @x = quantize(); }", 1); 125 | test("kprobe:f { quantize(); }", 1); 126 | } 127 | 128 | TEST(semantic_analyser, call_count) 129 | { 130 | test("kprobe:f { @x = count(); }", 0); 131 | test("kprobe:f { @x = count(1); }", 1); 132 | test("kprobe:f { count(); }", 1); 133 | } 134 | 135 | TEST(semantic_analyser, call_delete) 136 | { 137 | test("kprobe:f { @x = 1; delete(@x); }", 0); 138 | test("kprobe:f { delete(1); }", 1); 139 | test("kprobe:f { delete(); }", 1); 140 | test("kprobe:f { @y = delete(@x); }", 1); 141 | test("kprobe:f { $y = delete(@x); }", 1); 142 | } 143 | 144 | TEST(semantic_analyser, call_str) 145 | { 146 | test("kprobe:f { str(arg0); }", 0); 147 | test("kprobe:f { @x = str(arg0); }", 0); 148 | test("kprobe:f { str(); }", 1); 149 | test("kprobe:f { str(\"hello\"); }", 10); 150 | } 151 | 152 | TEST(semantic_analyser, call_sym) 153 | { 154 | test("kprobe:f { sym(arg0); }", 0); 155 | test("kprobe:f { @x = sym(arg0); }", 0); 156 | test("kprobe:f { sym(); }", 1); 157 | test("kprobe:f { sym(\"hello\"); }", 10); 158 | } 159 | 160 | TEST(semantic_analyser, call_usym) 161 | { 162 | test("kprobe:f { usym(arg0); }", 0); 163 | test("kprobe:f { @x = usym(arg0); }", 0); 164 | test("kprobe:f { usym(); }", 1); 165 | test("kprobe:f { usym(\"hello\"); }", 10); 166 | } 167 | 168 | TEST(semantic_analyser, call_reg) 169 | { 170 | test("kprobe:f { reg(\"ip\"); }", 0); 171 | test("kprobe:f { @x = reg(\"ip\"); }", 0); 172 | test("kprobe:f { reg(\"blah\"); }", 1); 173 | test("kprobe:f { reg(); }", 1); 174 | test("kprobe:f { reg(123); }", 1); 175 | } 176 | 177 | TEST(semantic_analyser, map_reassignment) 178 | { 179 | test("kprobe:f { @x = 1; @x = 2; }", 0); 180 | test("kprobe:f { @x = 1; @x = \"foo\"; }", 1); 181 | } 182 | 183 | TEST(semantic_analyser, variable_reassignment) 184 | { 185 | test("kprobe:f { $x = 1; $x = 2; }", 0); 186 | test("kprobe:f { $x = 1; $x = \"foo\"; }", 1); 187 | } 188 | 189 | TEST(semantic_analyser, map_use_before_assign) 190 | { 191 | test("kprobe:f { @x = @y; @y = 2; }", 0); 192 | } 193 | 194 | TEST(semantic_analyser, variable_use_before_assign) 195 | { 196 | test("kprobe:f { @x = $y; $y = 2; }", 1); 197 | } 198 | 199 | TEST(semantic_analyser, maps_are_global) 200 | { 201 | test("kprobe:f { @x = 1 } kprobe:g { @y = @x }", 0); 202 | test("kprobe:f { @x = 1 } kprobe:g { @x = \"abc\" }", 1); 203 | } 204 | 205 | TEST(semantic_analyser, variables_are_local) 206 | { 207 | test("kprobe:f { $x = 1 } kprobe:g { $x = \"abc\"; }", 0); 208 | test("kprobe:f { $x = 1 } kprobe:g { @y = $x }", 1); 209 | } 210 | 211 | TEST(semantic_analyser, variable_type) 212 | { 213 | Driver driver; 214 | test(driver, "kprobe:f { $x = 1 }", 0); 215 | SizedType st(Type::integer, 8); 216 | auto assignment = static_cast(driver.root_->probes->at(0)->stmts->at(0)); 217 | EXPECT_EQ(st, assignment->var->type); 218 | } 219 | 220 | TEST(semantic_analyser, printf) 221 | { 222 | test("kprobe:f { printf(\"hi\") }", 0); 223 | test("kprobe:f { printf(1234) }", 1); 224 | test("kprobe:f { printf() }", 1); 225 | test("kprobe:f { $fmt = \"mystring\"; printf($fmt) }", 1); 226 | test("kprobe:f { @x = printf(\"hi\") }", 1); 227 | test("kprobe:f { $x = printf(\"hi\") }", 1); 228 | } 229 | 230 | TEST(semantic_analyser, printf_format_int) 231 | { 232 | test("kprobe:f { printf(\"int: %d\", 1234) }", 0); 233 | test("kprobe:f { printf(\"int: %d\", pid) }", 0); 234 | test("kprobe:f { @x = 123; printf(\"int: %d\", @x) }", 0); 235 | test("kprobe:f { $x = 123; printf(\"int: %d\", $x) }", 0); 236 | 237 | test("kprobe:f { printf(\"int: %u\", 1234) }", 0); 238 | test("kprobe:f { printf(\"int: %x\", 1234) }", 0); 239 | test("kprobe:f { printf(\"int: %X\", 1234) }", 0); 240 | } 241 | 242 | TEST(semantic_analyser, printf_format_int_with_length) 243 | { 244 | test("kprobe:f { printf(\"int: %d\", 1234) }", 0); 245 | test("kprobe:f { printf(\"int: %u\", 1234) }", 0); 246 | test("kprobe:f { printf(\"int: %x\", 1234) }", 0); 247 | test("kprobe:f { printf(\"int: %X\", 1234) }", 0); 248 | test("kprobe:f { printf(\"int: %p\", 1234) }", 0); 249 | 250 | test("kprobe:f { printf(\"int: %hhd\", 1234) }", 0); 251 | test("kprobe:f { printf(\"int: %hhu\", 1234) }", 0); 252 | test("kprobe:f { printf(\"int: %hhx\", 1234) }", 0); 253 | test("kprobe:f { printf(\"int: %hhX\", 1234) }", 0); 254 | test("kprobe:f { printf(\"int: %hhp\", 1234) }", 0); 255 | 256 | test("kprobe:f { printf(\"int: %hd\", 1234) }", 0); 257 | test("kprobe:f { printf(\"int: %hu\", 1234) }", 0); 258 | test("kprobe:f { printf(\"int: %hx\", 1234) }", 0); 259 | test("kprobe:f { printf(\"int: %hX\", 1234) }", 0); 260 | test("kprobe:f { printf(\"int: %hp\", 1234) }", 0); 261 | 262 | test("kprobe:f { printf(\"int: %ld\", 1234) }", 0); 263 | test("kprobe:f { printf(\"int: %lu\", 1234) }", 0); 264 | test("kprobe:f { printf(\"int: %lx\", 1234) }", 0); 265 | test("kprobe:f { printf(\"int: %lX\", 1234) }", 0); 266 | test("kprobe:f { printf(\"int: %lp\", 1234) }", 0); 267 | 268 | test("kprobe:f { printf(\"int: %lld\", 1234) }", 0); 269 | test("kprobe:f { printf(\"int: %llu\", 1234) }", 0); 270 | test("kprobe:f { printf(\"int: %llx\", 1234) }", 0); 271 | test("kprobe:f { printf(\"int: %llX\", 1234) }", 0); 272 | test("kprobe:f { printf(\"int: %llp\", 1234) }", 0); 273 | 274 | test("kprobe:f { printf(\"int: %jd\", 1234) }", 0); 275 | test("kprobe:f { printf(\"int: %ju\", 1234) }", 0); 276 | test("kprobe:f { printf(\"int: %jx\", 1234) }", 0); 277 | test("kprobe:f { printf(\"int: %jX\", 1234) }", 0); 278 | test("kprobe:f { printf(\"int: %jp\", 1234) }", 0); 279 | 280 | test("kprobe:f { printf(\"int: %zd\", 1234) }", 0); 281 | test("kprobe:f { printf(\"int: %zu\", 1234) }", 0); 282 | test("kprobe:f { printf(\"int: %zx\", 1234) }", 0); 283 | test("kprobe:f { printf(\"int: %zX\", 1234) }", 0); 284 | test("kprobe:f { printf(\"int: %zp\", 1234) }", 0); 285 | 286 | test("kprobe:f { printf(\"int: %td\", 1234) }", 0); 287 | test("kprobe:f { printf(\"int: %tu\", 1234) }", 0); 288 | test("kprobe:f { printf(\"int: %tx\", 1234) }", 0); 289 | test("kprobe:f { printf(\"int: %tX\", 1234) }", 0); 290 | test("kprobe:f { printf(\"int: %tp\", 1234) }", 0); 291 | } 292 | 293 | TEST(semantic_analyser, printf_format_string) 294 | { 295 | test("kprobe:f { printf(\"str: %s\", \"mystr\") }", 0); 296 | test("kprobe:f { printf(\"str: %s\", comm) }", 0); 297 | test("kprobe:f { printf(\"str: %s\", str(arg0)) }", 0); 298 | test("kprobe:f { @x = \"hi\"; printf(\"str: %s\", @x) }", 0); 299 | test("kprobe:f { $x = \"hi\"; printf(\"str: %s\", $x) }", 0); 300 | } 301 | 302 | TEST(semantic_analyser, printf_bad_format_string) 303 | { 304 | test("kprobe:f { printf(\"%d\", \"mystr\") }", 10); 305 | test("kprobe:f { printf(\"%d\", str(arg0)) }", 10); 306 | 307 | test("kprobe:f { printf(\"%s\", 1234) }", 10); 308 | test("kprobe:f { printf(\"%s\", arg0) }", 10); 309 | } 310 | 311 | TEST(semantic_analyser, printf_format_multi) 312 | { 313 | test("kprobe:f { printf(\"%d %d %s\", 1, 2, \"mystr\") }", 0); 314 | test("kprobe:f { printf(\"%d %s %d\", 1, 2, \"mystr\") }", 10); 315 | } 316 | 317 | TEST(semantic_analyser, kprobe) 318 | { 319 | test("kprobe:f { 1 }", 0); 320 | test("kprobe:path:f { 1 }", 1); 321 | test("kprobe { 1 }", 1); 322 | 323 | test("kretprobe:f { 1 }", 0); 324 | test("kretprobe:path:f { 1 }", 1); 325 | test("kretprobe { 1 }", 1); 326 | } 327 | 328 | TEST(semantic_analyser, uprobe) 329 | { 330 | test("uprobe:path:f { 1 }", 0); 331 | test("uprobe:f { 1 }", 1); 332 | test("uprobe { 1 }", 1); 333 | 334 | test("uretprobe:path:f { 1 }", 0); 335 | test("uretprobe:f { 1 }", 1); 336 | test("uretprobe { 1 }", 1); 337 | } 338 | 339 | TEST(semantic_analyser, begin_end_probes) 340 | { 341 | test("BEGIN { 1 }", 0); 342 | test("BEGIN:f { 1 }", 1); 343 | test("BEGIN:path:f { 1 }", 1); 344 | test("BEGIN { 1 } BEGIN { 2 }", 10); 345 | 346 | test("END { 1 }", 0); 347 | test("END:f { 1 }", 1); 348 | test("END:path:f { 1 }", 1); 349 | test("END { 1 } END { 2 }", 10); 350 | } 351 | 352 | TEST(semantic_analyser, tracepoint) 353 | { 354 | test("tracepoint:category:event { 1 }", 0); 355 | test("tracepoint:f { 1 }", 1); 356 | test("tracepoint { 1 }", 1); 357 | } 358 | 359 | TEST(semantic_analyser, profile) 360 | { 361 | test("profile:hz:997 { 1 }", 0); 362 | test("profile:s:10 { 1 }", 0); 363 | test("profile:ms:100 { 1 }", 0); 364 | test("profile:us:100 { 1 }", 0); 365 | test("profile:ms:nan { 1 }", 1); 366 | test("profile:unit:100 { 1 }", 1); 367 | test("profile:f { 1 }", 1); 368 | test("profile { 1 }", 1); 369 | } 370 | 371 | TEST(semantic_analyser, variable_cast_types) 372 | { 373 | test("kprobe:f { $x = (type1)cpu; $x = (type1)cpu; }", 0); 374 | test("kprobe:f { $x = (type1)cpu; $x = (type2)cpu; }", 1); 375 | } 376 | 377 | TEST(semantic_analyser, map_cast_types) 378 | { 379 | test("kprobe:f { @x = (type1)cpu; @x = (type1)cpu; }", 0); 380 | test("kprobe:f { @x = (type1)cpu; @x = (type2)cpu; }", 1); 381 | } 382 | 383 | TEST(semantic_analyser, variable_casts_are_local) 384 | { 385 | test("kprobe:f { $x = (type1)cpu } kprobe:g { $x = (type2)cpu; }", 0); 386 | } 387 | 388 | TEST(semantic_analyser, map_casts_are_global) 389 | { 390 | test("kprobe:f { @x = (type1)cpu } kprobe:g { @x = (type2)cpu; }", 1); 391 | } 392 | 393 | TEST(semantic_analyser, cast_unknown_type) 394 | { 395 | test("kprobe:f { (faketype)cpu }", 1); 396 | } 397 | 398 | TEST(semantic_analyser, field_access) 399 | { 400 | test("kprobe:f { ((type1)cpu).field }", 0); 401 | test("kprobe:f { $x = (type1)cpu; $x.field }", 0); 402 | test("kprobe:f { @x = (type1)cpu; @x.field }", 0); 403 | } 404 | 405 | TEST(semantic_analyser, field_access_wrong_field) 406 | { 407 | test("kprobe:f { ((type1)cpu).blah }", 1); 408 | test("kprobe:f { $x = (type1)cpu; $x.blah }", 1); 409 | test("kprobe:f { @x = (type1)cpu; @x.blah }", 1); 410 | } 411 | 412 | TEST(semantic_analyser, field_access_wrong_expr) 413 | { 414 | test("kprobe:f { 1234->field }", 10); 415 | } 416 | 417 | TEST(semantic_analyser, field_access_types) 418 | { 419 | test("kprobe:f { ((type1)0).field == 123 }", 0); 420 | test("kprobe:f { ((type1)0).field == \"abc\" }", 10); 421 | 422 | test("kprobe:f { ((type1)0).mystr == \"abc\" }", 0); 423 | test("kprobe:f { ((type1)0).mystr == 123 }", 10); 424 | 425 | test("kprobe:f { ((type1)0).field == ((type2)0).field }", 0); 426 | test("kprobe:f { ((type1)0).mystr == ((type2)0).field }", 10); 427 | } 428 | 429 | TEST(semantic_analyser, field_access_pointer) 430 | { 431 | test("kprobe:f { ((type1*)0)->field }", 0); 432 | test("kprobe:f { ((type1*)0).field }", 1); 433 | test("kprobe:f { *((type1*)0) }", 0); 434 | } 435 | 436 | TEST(semantic_analyser, field_access_sub_struct) 437 | { 438 | test("kprobe:f { ((type1)0).type2ptr->field }", 0); 439 | test("kprobe:f { ((type1)0).type2.field }", 0); 440 | test("kprobe:f { $x = (type2)0; $x = ((type1)0).type2 }", 0); 441 | test("kprobe:f { $x = (type2*)0; $x = ((type1)0).type2ptr }", 0); 442 | test("kprobe:f { $x = (type1)0; $x = ((type1)0).type2 }", 1); 443 | test("kprobe:f { $x = (type1*)0; $x = ((type1)0).type2ptr }", 1); 444 | } 445 | 446 | } // namespace semantic_analyser 447 | } // namespace test 448 | } // namespace bpftrace 449 | -------------------------------------------------------------------------------- /tests/bpftrace.cpp: -------------------------------------------------------------------------------- 1 | #include "gmock/gmock.h" 2 | #include "gtest/gtest.h" 3 | #include "bpftrace.h" 4 | 5 | namespace bpftrace { 6 | namespace test { 7 | namespace bpftrace { 8 | 9 | class MockBPFtrace : public BPFtrace { 10 | public: 11 | MOCK_METHOD3(find_wildcard_matches, std::set( 12 | const std::string &prefix, 13 | const std::string &func, 14 | const std::string &file_name)); 15 | std::vector get_probes() 16 | { 17 | return probes_; 18 | } 19 | std::vector get_special_probes() 20 | { 21 | return special_probes_; 22 | } 23 | }; 24 | 25 | using ::testing::_; 26 | using ::testing::ContainerEq; 27 | using ::testing::Return; 28 | using ::testing::StrictMock; 29 | 30 | void check_kprobe(Probe &p, const std::string &attach_point, const std::string &prog_name) 31 | { 32 | EXPECT_EQ(ProbeType::kprobe, p.type); 33 | EXPECT_EQ(attach_point, p.attach_point); 34 | EXPECT_EQ(prog_name, p.prog_name); 35 | EXPECT_EQ("kprobe:" + attach_point, p.name); 36 | } 37 | 38 | void check_uprobe(Probe &p, const std::string &path, const std::string &attach_point, const std::string &prog_name) 39 | { 40 | EXPECT_EQ(ProbeType::uprobe, p.type); 41 | EXPECT_EQ(attach_point, p.attach_point); 42 | EXPECT_EQ(prog_name, p.prog_name); 43 | EXPECT_EQ("uprobe:" + path + ":" + attach_point, p.name); 44 | } 45 | 46 | void check_tracepoint(Probe &p, const std::string &target, const std::string &func, const std::string &prog_name) 47 | { 48 | EXPECT_EQ(ProbeType::tracepoint, p.type); 49 | EXPECT_EQ(func, p.attach_point); 50 | EXPECT_EQ(prog_name, p.prog_name); 51 | EXPECT_EQ("tracepoint:" + target + ":" + func, p.name); 52 | } 53 | 54 | void check_profile(Probe &p, const std::string &unit, int freq, const std::string &prog_name) 55 | { 56 | EXPECT_EQ(ProbeType::profile, p.type); 57 | EXPECT_EQ(freq, p.freq); 58 | EXPECT_EQ(prog_name, p.prog_name); 59 | EXPECT_EQ("profile:" + unit + ":" + std::to_string(freq), p.name); 60 | } 61 | 62 | void check_special_probe(Probe &p, const std::string &attach_point, const std::string &prog_name) 63 | { 64 | EXPECT_EQ(ProbeType::uprobe, p.type); 65 | EXPECT_EQ(attach_point, p.attach_point); 66 | EXPECT_EQ(prog_name, p.prog_name); 67 | EXPECT_EQ(prog_name, p.name); 68 | } 69 | 70 | TEST(bpftrace, add_begin_probe) 71 | { 72 | ast::AttachPoint a("BEGIN"); 73 | ast::AttachPointList attach_points = { &a }; 74 | ast::Probe probe(&attach_points, nullptr, nullptr); 75 | 76 | StrictMock bpftrace; 77 | EXPECT_EQ(0, bpftrace.add_probe(probe)); 78 | EXPECT_EQ(0, bpftrace.get_probes().size()); 79 | EXPECT_EQ(1, bpftrace.get_special_probes().size()); 80 | 81 | check_special_probe(bpftrace.get_special_probes().at(0), "BEGIN_trigger", "BEGIN"); 82 | } 83 | 84 | TEST(bpftrace, add_end_probe) 85 | { 86 | ast::AttachPoint a("END"); 87 | ast::AttachPointList attach_points = { &a }; 88 | ast::Probe probe(&attach_points, nullptr, nullptr); 89 | 90 | StrictMock bpftrace; 91 | EXPECT_EQ(0, bpftrace.add_probe(probe)); 92 | EXPECT_EQ(0, bpftrace.get_probes().size()); 93 | EXPECT_EQ(1, bpftrace.get_special_probes().size()); 94 | 95 | check_special_probe(bpftrace.get_special_probes().at(0), "END_trigger", "END"); 96 | } 97 | 98 | TEST(bpftrace, add_probes_single) 99 | { 100 | ast::AttachPoint a("kprobe", "sys_read"); 101 | ast::AttachPointList attach_points = { &a }; 102 | ast::Probe probe(&attach_points, nullptr, nullptr); 103 | 104 | StrictMock bpftrace; 105 | EXPECT_EQ(0, bpftrace.add_probe(probe)); 106 | EXPECT_EQ(1, bpftrace.get_probes().size()); 107 | EXPECT_EQ(0, bpftrace.get_special_probes().size()); 108 | 109 | check_kprobe(bpftrace.get_probes().at(0), "sys_read", "kprobe:sys_read"); 110 | } 111 | 112 | TEST(bpftrace, add_probes_multiple) 113 | { 114 | ast::AttachPoint a1("kprobe", "sys_read"); 115 | ast::AttachPoint a2("kprobe", "sys_write"); 116 | ast::AttachPointList attach_points = { &a1, &a2 }; 117 | ast::Probe probe(&attach_points, nullptr, nullptr); 118 | 119 | StrictMock bpftrace; 120 | EXPECT_EQ(0, bpftrace.add_probe(probe)); 121 | EXPECT_EQ(2, bpftrace.get_probes().size()); 122 | EXPECT_EQ(0, bpftrace.get_special_probes().size()); 123 | 124 | std::string probe_prog_name = "kprobe:sys_read,kprobe:sys_write"; 125 | check_kprobe(bpftrace.get_probes().at(0), "sys_read", probe_prog_name); 126 | check_kprobe(bpftrace.get_probes().at(1), "sys_write", probe_prog_name); 127 | } 128 | 129 | TEST(bpftrace, add_probes_character_class) 130 | { 131 | ast::AttachPoint a1("kprobe", "[Ss]y[Ss]_read"); 132 | ast::AttachPoint a2("kprobe", "sys_write"); 133 | ast::AttachPointList attach_points = { &a1, &a2 }; 134 | ast::Probe probe(&attach_points, nullptr, nullptr); 135 | 136 | StrictMock bpftrace; 137 | std::set matches = { "SyS_read", "sys_read" }; 138 | ON_CALL(bpftrace, find_wildcard_matches(_, _, _)) 139 | .WillByDefault(Return(matches)); 140 | EXPECT_CALL(bpftrace, 141 | find_wildcard_matches("", "[Ss]y[Ss]_read", 142 | "/sys/kernel/debug/tracing/available_filter_functions")) 143 | .Times(1); 144 | 145 | EXPECT_EQ(0, bpftrace.add_probe(probe)); 146 | EXPECT_EQ(3, bpftrace.get_probes().size()); 147 | EXPECT_EQ(0, bpftrace.get_special_probes().size()); 148 | 149 | std::string probe_prog_name = "kprobe:[Ss]y[Ss]_read,kprobe:sys_write"; 150 | check_kprobe(bpftrace.get_probes().at(0), "SyS_read", probe_prog_name); 151 | check_kprobe(bpftrace.get_probes().at(1), "sys_read", probe_prog_name); 152 | check_kprobe(bpftrace.get_probes().at(2), "sys_write", probe_prog_name); 153 | } 154 | 155 | TEST(bpftrace, add_probes_wildcard) 156 | { 157 | ast::AttachPoint a1("kprobe", "sys_read"); 158 | ast::AttachPoint a2("kprobe", "my_*"); 159 | ast::AttachPoint a3("kprobe", "sys_write"); 160 | ast::AttachPointList attach_points = { &a1, &a2, &a3 }; 161 | ast::Probe probe(&attach_points, nullptr, nullptr); 162 | 163 | StrictMock bpftrace; 164 | std::set matches = { "my_one", "my_two" }; 165 | ON_CALL(bpftrace, find_wildcard_matches(_, _, _)) 166 | .WillByDefault(Return(matches)); 167 | EXPECT_CALL(bpftrace, 168 | find_wildcard_matches("", "my_*", 169 | "/sys/kernel/debug/tracing/available_filter_functions")) 170 | .Times(1); 171 | 172 | EXPECT_EQ(0, bpftrace.add_probe(probe)); 173 | EXPECT_EQ(4, bpftrace.get_probes().size()); 174 | EXPECT_EQ(0, bpftrace.get_special_probes().size()); 175 | 176 | std::string probe_prog_name = "kprobe:sys_read,kprobe:my_*,kprobe:sys_write"; 177 | check_kprobe(bpftrace.get_probes().at(0), "sys_read", probe_prog_name); 178 | check_kprobe(bpftrace.get_probes().at(1), "my_one", probe_prog_name); 179 | check_kprobe(bpftrace.get_probes().at(2), "my_two", probe_prog_name); 180 | check_kprobe(bpftrace.get_probes().at(3), "sys_write", probe_prog_name); 181 | } 182 | 183 | TEST(bpftrace, add_probes_wildcard_no_matches) 184 | { 185 | ast::AttachPoint a1("kprobe", "sys_read"); 186 | ast::AttachPoint a2("kprobe", "my_*"); 187 | ast::AttachPoint a3("kprobe", "sys_write"); 188 | ast::AttachPointList attach_points = { &a1, &a2, &a3 }; 189 | ast::Probe probe(&attach_points, nullptr, nullptr); 190 | 191 | StrictMock bpftrace; 192 | std::set matches; 193 | ON_CALL(bpftrace, find_wildcard_matches(_, _, _)) 194 | .WillByDefault(Return(matches)); 195 | EXPECT_CALL(bpftrace, 196 | find_wildcard_matches("", "my_*", 197 | "/sys/kernel/debug/tracing/available_filter_functions")) 198 | .Times(1); 199 | 200 | EXPECT_EQ(0, bpftrace.add_probe(probe)); 201 | EXPECT_EQ(2, bpftrace.get_probes().size()); 202 | EXPECT_EQ(0, bpftrace.get_special_probes().size()); 203 | 204 | std::string probe_prog_name = "kprobe:sys_read,kprobe:my_*,kprobe:sys_write"; 205 | check_kprobe(bpftrace.get_probes().at(0), "sys_read", probe_prog_name); 206 | check_kprobe(bpftrace.get_probes().at(1), "sys_write", probe_prog_name); 207 | } 208 | 209 | TEST(bpftrace, add_probes_uprobe) 210 | { 211 | ast::AttachPoint a("uprobe", "/bin/sh", "foo"); 212 | ast::AttachPointList attach_points = { &a }; 213 | ast::Probe probe(&attach_points, nullptr, nullptr); 214 | 215 | StrictMock bpftrace; 216 | 217 | EXPECT_EQ(0, bpftrace.add_probe(probe)); 218 | EXPECT_EQ(1, bpftrace.get_probes().size()); 219 | EXPECT_EQ(0, bpftrace.get_special_probes().size()); 220 | check_uprobe(bpftrace.get_probes().at(0), "/bin/sh", "foo", "uprobe:/bin/sh:foo"); 221 | } 222 | 223 | TEST(bpftrace, add_probes_uprobe_wildcard) 224 | { 225 | ast::AttachPoint a("uprobe", "/bin/sh", "foo*"); 226 | ast::AttachPointList attach_points = { &a }; 227 | ast::Probe probe(&attach_points, nullptr, nullptr); 228 | 229 | StrictMock bpftrace; 230 | 231 | EXPECT_NE(bpftrace.add_probe(probe), 0); 232 | EXPECT_EQ(0, bpftrace.get_probes().size()); 233 | EXPECT_EQ(0, bpftrace.get_special_probes().size()); 234 | } 235 | 236 | TEST(bpftrace, add_probes_tracepoint) 237 | { 238 | ast::AttachPoint a("tracepoint", "sched", "sched_switch"); 239 | ast::AttachPointList attach_points = { &a }; 240 | ast::Probe probe(&attach_points, nullptr, nullptr); 241 | 242 | StrictMock bpftrace; 243 | 244 | EXPECT_EQ(0, bpftrace.add_probe(probe)); 245 | EXPECT_EQ(1, bpftrace.get_probes().size()); 246 | EXPECT_EQ(0, bpftrace.get_special_probes().size()); 247 | 248 | std::string probe_prog_name = "tracepoint:sched:sched_switch"; 249 | check_tracepoint(bpftrace.get_probes().at(0), "sched", "sched_switch", probe_prog_name); 250 | } 251 | 252 | TEST(bpftrace, add_probes_tracepoint_wildcard) 253 | { 254 | ast::AttachPoint a("tracepoint", "sched", "sched_*"); 255 | ast::AttachPointList attach_points = { &a }; 256 | ast::Probe probe(&attach_points, nullptr, nullptr); 257 | 258 | StrictMock bpftrace; 259 | std::set matches = { "sched_one", "sched_two" }; 260 | ON_CALL(bpftrace, find_wildcard_matches(_, _, _)) 261 | .WillByDefault(Return(matches)); 262 | EXPECT_CALL(bpftrace, 263 | find_wildcard_matches("sched", "sched_*", 264 | "/sys/kernel/debug/tracing/available_events")) 265 | .Times(1); 266 | 267 | EXPECT_EQ(bpftrace.add_probe(probe), 0); 268 | EXPECT_EQ(2, bpftrace.get_probes().size()); 269 | EXPECT_EQ(0, bpftrace.get_special_probes().size()); 270 | 271 | std::string probe_prog_name = "tracepoint:sched:sched_*"; 272 | check_tracepoint(bpftrace.get_probes().at(0), "sched", "sched_one", probe_prog_name); 273 | check_tracepoint(bpftrace.get_probes().at(1), "sched", "sched_two", probe_prog_name); 274 | } 275 | 276 | TEST(bpftrace, add_probes_tracepoint_wildcard_no_matches) 277 | { 278 | ast::AttachPoint a("tracepoint", "typo", "typo_*"); 279 | ast::AttachPointList attach_points = { &a }; 280 | ast::Probe probe(&attach_points, nullptr, nullptr); 281 | 282 | StrictMock bpftrace; 283 | std::set matches; 284 | ON_CALL(bpftrace, find_wildcard_matches(_, _, _)) 285 | .WillByDefault(Return(matches)); 286 | EXPECT_CALL(bpftrace, 287 | find_wildcard_matches("typo", "typo_*", 288 | "/sys/kernel/debug/tracing/available_events")) 289 | .Times(1); 290 | 291 | EXPECT_EQ(0, bpftrace.add_probe(probe)); 292 | EXPECT_EQ(0, bpftrace.get_probes().size()); 293 | EXPECT_EQ(0, bpftrace.get_special_probes().size()); 294 | } 295 | 296 | TEST(bpftrace, add_probes_profile) 297 | { 298 | ast::AttachPoint a("profile", "ms", 997); 299 | ast::AttachPointList attach_points = { &a }; 300 | ast::Probe probe(&attach_points, nullptr, nullptr); 301 | 302 | StrictMock bpftrace; 303 | 304 | EXPECT_EQ(0, bpftrace.add_probe(probe)); 305 | EXPECT_EQ(1, bpftrace.get_probes().size()); 306 | EXPECT_EQ(0, bpftrace.get_special_probes().size()); 307 | 308 | std::string probe_prog_name = "profile:ms:997"; 309 | check_profile(bpftrace.get_probes().at(0), "ms", 997, probe_prog_name); 310 | } 311 | 312 | std::pair, std::vector> key_value_pair_int(std::vector key, int val) 313 | { 314 | std::pair, std::vector> pair; 315 | pair.first = std::vector(sizeof(uint64_t)*key.size()); 316 | pair.second = std::vector(sizeof(uint64_t)); 317 | 318 | uint8_t *key_data = pair.first.data(); 319 | uint8_t *val_data = pair.second.data(); 320 | 321 | for (size_t i=0; i, std::vector> key_value_pair_str(std::vector key, int val) 331 | { 332 | std::pair, std::vector> pair; 333 | pair.first = std::vector(STRING_SIZE*key.size()); 334 | pair.second = std::vector(sizeof(uint64_t)); 335 | 336 | uint8_t *key_data = pair.first.data(); 337 | uint8_t *val_data = pair.second.data(); 338 | 339 | for (size_t i=0; i, std::vector> key_value_pair_int_str(int myint, std::string mystr, int val) 349 | { 350 | std::pair, std::vector> pair; 351 | pair.first = std::vector(sizeof(uint64_t) + STRING_SIZE); 352 | pair.second = std::vector(sizeof(uint64_t)); 353 | 354 | uint8_t *key_data = pair.first.data(); 355 | uint8_t *val_data = pair.second.data(); 356 | 357 | *(uint64_t*)key_data = myint; 358 | strncpy((char*)key_data + sizeof(uint64_t), mystr.c_str(), STRING_SIZE); 359 | *(uint64_t*)val_data = val; 360 | 361 | return pair; 362 | } 363 | 364 | TEST(bpftrace, sort_by_key_int) 365 | { 366 | StrictMock bpftrace; 367 | 368 | std::vector key_args = { SizedType(Type::integer, 8) }; 369 | std::vector, std::vector>> values_by_key = 370 | { 371 | key_value_pair_int({2}, 12), 372 | key_value_pair_int({3}, 11), 373 | key_value_pair_int({1}, 10), 374 | }; 375 | bpftrace.sort_by_key(key_args, values_by_key); 376 | 377 | std::vector, std::vector>> expected_values = 378 | { 379 | key_value_pair_int({1}, 10), 380 | key_value_pair_int({2}, 12), 381 | key_value_pair_int({3}, 11), 382 | }; 383 | 384 | EXPECT_THAT(values_by_key, ContainerEq(expected_values)); 385 | } 386 | 387 | TEST(bpftrace, sort_by_key_int_int) 388 | { 389 | StrictMock bpftrace; 390 | 391 | std::vector key_args = { 392 | SizedType(Type::integer, 8), 393 | SizedType(Type::integer, 8), 394 | SizedType(Type::integer, 8), 395 | }; 396 | std::vector, std::vector>> values_by_key = 397 | { 398 | key_value_pair_int({5,2,1}, 1), 399 | key_value_pair_int({5,3,1}, 2), 400 | key_value_pair_int({5,1,1}, 3), 401 | key_value_pair_int({2,2,2}, 4), 402 | key_value_pair_int({2,3,2}, 5), 403 | key_value_pair_int({2,1,2}, 6), 404 | }; 405 | bpftrace.sort_by_key(key_args, values_by_key); 406 | 407 | std::vector, std::vector>> expected_values = 408 | { 409 | key_value_pair_int({2,1,2}, 6), 410 | key_value_pair_int({2,2,2}, 4), 411 | key_value_pair_int({2,3,2}, 5), 412 | key_value_pair_int({5,1,1}, 3), 413 | key_value_pair_int({5,2,1}, 1), 414 | key_value_pair_int({5,3,1}, 2), 415 | }; 416 | 417 | EXPECT_THAT(values_by_key, ContainerEq(expected_values)); 418 | } 419 | 420 | TEST(bpftrace, sort_by_key_str) 421 | { 422 | StrictMock bpftrace; 423 | 424 | std::vector key_args = { 425 | SizedType(Type::string, STRING_SIZE), 426 | }; 427 | std::vector, std::vector>> values_by_key = 428 | { 429 | key_value_pair_str({"z"}, 1), 430 | key_value_pair_str({"a"}, 2), 431 | key_value_pair_str({"x"}, 3), 432 | key_value_pair_str({"d"}, 4), 433 | }; 434 | bpftrace.sort_by_key(key_args, values_by_key); 435 | 436 | std::vector, std::vector>> expected_values = 437 | { 438 | key_value_pair_str({"a"}, 2), 439 | key_value_pair_str({"d"}, 4), 440 | key_value_pair_str({"x"}, 3), 441 | key_value_pair_str({"z"}, 1), 442 | }; 443 | 444 | EXPECT_THAT(values_by_key, ContainerEq(expected_values)); 445 | } 446 | 447 | TEST(bpftrace, sort_by_key_str_str) 448 | { 449 | StrictMock bpftrace; 450 | 451 | std::vector key_args = { 452 | SizedType(Type::string, STRING_SIZE), 453 | SizedType(Type::string, STRING_SIZE), 454 | SizedType(Type::string, STRING_SIZE), 455 | }; 456 | std::vector, std::vector>> values_by_key = 457 | { 458 | key_value_pair_str({"z", "a", "l"}, 1), 459 | key_value_pair_str({"a", "a", "m"}, 2), 460 | key_value_pair_str({"z", "c", "n"}, 3), 461 | key_value_pair_str({"a", "c", "o"}, 4), 462 | key_value_pair_str({"z", "b", "p"}, 5), 463 | key_value_pair_str({"a", "b", "q"}, 6), 464 | }; 465 | bpftrace.sort_by_key(key_args, values_by_key); 466 | 467 | std::vector, std::vector>> expected_values = 468 | { 469 | key_value_pair_str({"a", "a", "m"}, 2), 470 | key_value_pair_str({"a", "b", "q"}, 6), 471 | key_value_pair_str({"a", "c", "o"}, 4), 472 | key_value_pair_str({"z", "a", "l"}, 1), 473 | key_value_pair_str({"z", "b", "p"}, 5), 474 | key_value_pair_str({"z", "c", "n"}, 3), 475 | }; 476 | 477 | EXPECT_THAT(values_by_key, ContainerEq(expected_values)); 478 | } 479 | 480 | TEST(bpftrace, sort_by_key_int_str) 481 | { 482 | StrictMock bpftrace; 483 | 484 | std::vector key_args = { 485 | SizedType(Type::integer, 8), 486 | SizedType(Type::string, STRING_SIZE), 487 | }; 488 | std::vector, std::vector>> values_by_key = 489 | { 490 | key_value_pair_int_str(1, "b", 1), 491 | key_value_pair_int_str(2, "b", 2), 492 | key_value_pair_int_str(3, "b", 3), 493 | key_value_pair_int_str(1, "a", 4), 494 | key_value_pair_int_str(2, "a", 5), 495 | key_value_pair_int_str(3, "a", 6), 496 | }; 497 | bpftrace.sort_by_key(key_args, values_by_key); 498 | 499 | std::vector, std::vector>> expected_values = 500 | { 501 | key_value_pair_int_str(1, "a", 4), 502 | key_value_pair_int_str(1, "b", 1), 503 | key_value_pair_int_str(2, "a", 5), 504 | key_value_pair_int_str(2, "b", 2), 505 | key_value_pair_int_str(3, "a", 6), 506 | key_value_pair_int_str(3, "b", 3), 507 | }; 508 | 509 | EXPECT_THAT(values_by_key, ContainerEq(expected_values)); 510 | } 511 | 512 | } // namespace bpftrace 513 | } // namespace test 514 | } // namespace bpftrace 515 | -------------------------------------------------------------------------------- /src/bpftrace.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "bcc_syms.h" 10 | #include "perf_reader.h" 11 | 12 | #include "bpforc.h" 13 | #include "bpftrace.h" 14 | #include "attached_probe.h" 15 | #include "triggers.h" 16 | 17 | namespace bpftrace { 18 | 19 | int BPFtrace::add_probe(ast::Probe &p) 20 | { 21 | for (auto attach_point : *p.attach_points) 22 | { 23 | if (attach_point->provider == "BEGIN") 24 | { 25 | Probe probe; 26 | probe.path = "/proc/self/exe"; 27 | probe.attach_point = "BEGIN_trigger"; 28 | probe.type = probetype(attach_point->provider); 29 | probe.prog_name = p.name(); 30 | probe.name = p.name(); 31 | special_probes_.push_back(probe); 32 | continue; 33 | } 34 | else if (attach_point->provider == "END") 35 | { 36 | Probe probe; 37 | probe.path = "/proc/self/exe"; 38 | probe.attach_point = "END_trigger"; 39 | probe.type = probetype(attach_point->provider); 40 | probe.prog_name = p.name(); 41 | probe.name = p.name(); 42 | special_probes_.push_back(probe); 43 | continue; 44 | } 45 | 46 | std::vector attach_funcs; 47 | if (attach_point->func.find("*") != std::string::npos || 48 | attach_point->func.find("[") != std::string::npos && 49 | attach_point->func.find("]") != std::string::npos) 50 | { 51 | std::string file_name; 52 | switch (probetype(attach_point->provider)) 53 | { 54 | case ProbeType::kprobe: 55 | case ProbeType::kretprobe: 56 | file_name = "/sys/kernel/debug/tracing/available_filter_functions"; 57 | break; 58 | case ProbeType::tracepoint: 59 | file_name = "/sys/kernel/debug/tracing/available_events"; 60 | break; 61 | default: 62 | std::cerr << "Wildcard matches aren't available on probe type '" 63 | << attach_point->provider << "'" << std::endl; 64 | return 1; 65 | } 66 | auto matches = find_wildcard_matches(attach_point->target, 67 | attach_point->func, 68 | file_name); 69 | attach_funcs.insert(attach_funcs.end(), matches.begin(), matches.end()); 70 | } 71 | else 72 | { 73 | attach_funcs.push_back(attach_point->func); 74 | } 75 | 76 | for (auto func : attach_funcs) 77 | { 78 | Probe probe; 79 | probe.path = attach_point->target; 80 | probe.attach_point = func; 81 | probe.type = probetype(attach_point->provider); 82 | probe.prog_name = p.name(); 83 | probe.name = attach_point->name(func); 84 | probe.freq = attach_point->freq; 85 | probes_.push_back(probe); 86 | } 87 | } 88 | 89 | return 0; 90 | } 91 | 92 | std::set BPFtrace::find_wildcard_matches(const std::string &prefix, const std::string &func, const std::string &file_name) 93 | { 94 | // Turn glob into a regex 95 | auto regex_str = "(" + std::regex_replace(func, std::regex("\\*"), "[^\\s]*") + ")"; 96 | if (prefix != "") 97 | regex_str = prefix + ":" + regex_str; 98 | regex_str = "^" + regex_str; 99 | std::regex func_regex(regex_str); 100 | std::smatch match; 101 | 102 | std::ifstream file(file_name); 103 | if (file.fail()) 104 | { 105 | std::cerr << strerror(errno) << ": " << file_name << std::endl; 106 | return std::set(); 107 | } 108 | 109 | std::string line; 110 | std::set matches; 111 | while (std::getline(file, line)) 112 | { 113 | if (std::regex_search(line, match, func_regex)) 114 | { 115 | assert(match.size() == 2); 116 | matches.insert(match[1]); 117 | } 118 | } 119 | return matches; 120 | } 121 | 122 | int BPFtrace::num_probes() const 123 | { 124 | return special_probes_.size() + probes_.size(); 125 | } 126 | 127 | void perf_event_printer(void *cb_cookie, void *data, int size) 128 | { 129 | auto bpftrace = static_cast(cb_cookie); 130 | auto printf_id = *static_cast(data); 131 | auto arg_data = static_cast(data) + sizeof(uint64_t); 132 | 133 | auto fmt = std::get<0>(bpftrace->printf_args_[printf_id]).c_str(); 134 | auto args = std::get<1>(bpftrace->printf_args_[printf_id]); 135 | std::vector arg_values; 136 | std::vector> resolved_symbols; 137 | for (auto arg : args) 138 | { 139 | switch (arg.type) 140 | { 141 | case Type::integer: 142 | arg_values.push_back(*(uint64_t*)arg_data); 143 | break; 144 | case Type::string: 145 | arg_values.push_back((uint64_t)arg_data); 146 | break; 147 | case Type::sym: 148 | resolved_symbols.emplace_back(strdup( 149 | bpftrace->resolve_sym(*(uint64_t*)arg_data).c_str())); 150 | arg_values.push_back((uint64_t)resolved_symbols.back().get()); 151 | break; 152 | case Type::usym: 153 | resolved_symbols.emplace_back(strdup( 154 | bpftrace->resolve_usym(*(uint64_t*)arg_data).c_str())); 155 | arg_values.push_back((uint64_t)resolved_symbols.back().get()); 156 | break; 157 | default: 158 | abort(); 159 | } 160 | arg_data += arg.size; 161 | } 162 | 163 | switch (args.size()) 164 | { 165 | case 0: 166 | printf(fmt); 167 | break; 168 | case 1: 169 | printf(fmt, arg_values.at(0)); 170 | break; 171 | case 2: 172 | printf(fmt, arg_values.at(0), arg_values.at(1)); 173 | break; 174 | case 3: 175 | printf(fmt, arg_values.at(0), arg_values.at(1), arg_values.at(2)); 176 | break; 177 | case 4: 178 | printf(fmt, arg_values.at(0), arg_values.at(1), arg_values.at(2), 179 | arg_values.at(3)); 180 | break; 181 | case 5: 182 | printf(fmt, arg_values.at(0), arg_values.at(1), arg_values.at(2), 183 | arg_values.at(3), arg_values.at(4)); 184 | break; 185 | case 6: 186 | printf(fmt, arg_values.at(0), arg_values.at(1), arg_values.at(2), 187 | arg_values.at(3), arg_values.at(4), arg_values.at(5)); 188 | break; 189 | default: 190 | abort(); 191 | } 192 | } 193 | 194 | void perf_event_lost(void *cb_cookie, uint64_t lost) 195 | { 196 | printf("Lost %lu events\n", lost); 197 | } 198 | 199 | std::unique_ptr BPFtrace::attach_probe(Probe &probe, const BpfOrc &bpforc) 200 | { 201 | auto func = bpforc.sections_.find("s_" + probe.prog_name); 202 | if (func == bpforc.sections_.end()) 203 | { 204 | std::cerr << "Code not generated for probe: " << probe.name << std::endl; 205 | return nullptr; 206 | } 207 | try 208 | { 209 | return std::make_unique(probe, func->second); 210 | } 211 | catch (std::runtime_error e) 212 | { 213 | std::cerr << e.what() << std::endl; 214 | } 215 | return nullptr; 216 | } 217 | 218 | int BPFtrace::run(std::unique_ptr bpforc) 219 | { 220 | for (Probe &probe : special_probes_) 221 | { 222 | auto attached_probe = attach_probe(probe, *bpforc.get()); 223 | if (attached_probe == nullptr) 224 | return -1; 225 | special_attached_probes_.push_back(std::move(attached_probe)); 226 | } 227 | 228 | int epollfd = setup_perf_events(); 229 | if (epollfd < 0) 230 | return epollfd; 231 | 232 | BEGIN_trigger(); 233 | 234 | for (Probe &probe : probes_) 235 | { 236 | auto attached_probe = attach_probe(probe, *bpforc.get()); 237 | if (attached_probe == nullptr) 238 | return -1; 239 | attached_probes_.push_back(std::move(attached_probe)); 240 | } 241 | 242 | poll_perf_events(epollfd); 243 | attached_probes_.clear(); 244 | 245 | END_trigger(); 246 | poll_perf_events(epollfd, 100); 247 | special_attached_probes_.clear(); 248 | 249 | return 0; 250 | } 251 | 252 | int BPFtrace::setup_perf_events() 253 | { 254 | int epollfd = epoll_create1(EPOLL_CLOEXEC); 255 | if (epollfd == -1) 256 | { 257 | std::cerr << "Failed to create epollfd" << std::endl; 258 | return -1; 259 | } 260 | 261 | std::vector cpus = ebpf::get_online_cpus(); 262 | online_cpus_ = cpus.size(); 263 | for (int cpu : cpus) 264 | { 265 | int page_cnt = 8; 266 | void *reader = bpf_open_perf_buffer(&perf_event_printer, &perf_event_lost, this, -1, cpu, page_cnt); 267 | if (reader == nullptr) 268 | { 269 | std::cerr << "Failed to open perf buffer" << std::endl; 270 | return -1; 271 | } 272 | 273 | struct epoll_event ev = {}; 274 | ev.events = EPOLLIN; 275 | ev.data.ptr = reader; 276 | int reader_fd = perf_reader_fd((perf_reader*)reader); 277 | 278 | bpf_update_elem(perf_event_map_->mapfd_, &cpu, &reader_fd, 0); 279 | if (epoll_ctl(epollfd, EPOLL_CTL_ADD, reader_fd, &ev) == -1) 280 | { 281 | std::cerr << "Failed to add perf reader to epoll" << std::endl; 282 | return -1; 283 | } 284 | } 285 | return epollfd; 286 | } 287 | 288 | void BPFtrace::poll_perf_events(int epollfd, int timeout) 289 | { 290 | auto events = std::vector(online_cpus_); 291 | while (true) 292 | { 293 | int ready = epoll_wait(epollfd, events.data(), online_cpus_, timeout); 294 | if (ready <= 0) 295 | { 296 | return; 297 | } 298 | 299 | for (int i=0; i old_key; 328 | try 329 | { 330 | old_key = find_empty_key(map, map.key_.size()); 331 | } 332 | catch (std::runtime_error &e) 333 | { 334 | std::cerr << "Error getting key for map '" << map.name_ << "': " 335 | << e.what() << std::endl; 336 | return -2; 337 | } 338 | auto key(old_key); 339 | 340 | std::vector, std::vector>> values_by_key; 341 | 342 | while (bpf_get_next_key(map.mapfd_, old_key.data(), key.data()) == 0) 343 | { 344 | int value_size = map.type_.size; 345 | if (map.type_.type == Type::count) 346 | value_size *= ncpus_; 347 | auto value = std::vector(value_size); 348 | int err = bpf_lookup_elem(map.mapfd_, key.data(), value.data()); 349 | if (err) 350 | { 351 | std::cerr << "Error looking up elem: " << err << std::endl; 352 | return -1; 353 | } 354 | 355 | values_by_key.push_back({key, value}); 356 | 357 | old_key = key; 358 | } 359 | 360 | if (map.type_.type == Type::count) 361 | { 362 | std::sort(values_by_key.begin(), values_by_key.end(), [&](auto &a, auto &b) 363 | { 364 | return reduce_value(a.second, ncpus_) < reduce_value(b.second, ncpus_); 365 | }); 366 | } 367 | else 368 | { 369 | sort_by_key(map.key_.args_, values_by_key); 370 | }; 371 | 372 | for (auto &pair : values_by_key) 373 | { 374 | auto key = pair.first; 375 | auto value = pair.second; 376 | 377 | std::cout << map.name_ << map.key_.argument_value_list(*this, key) << ": "; 378 | 379 | if (map.type_.type == Type::stack) 380 | std::cout << get_stack(*(uint32_t*)value.data(), false, 8); 381 | else if (map.type_.type == Type::ustack) 382 | std::cout << get_stack(*(uint32_t*)value.data(), true, 8); 383 | else if (map.type_.type == Type::sym) 384 | std::cout << resolve_sym(*(uintptr_t*)value.data()); 385 | else if (map.type_.type == Type::usym) 386 | std::cout << resolve_usym(*(uintptr_t*)value.data()); 387 | else if (map.type_.type == Type::string) 388 | std::cout << value.data() << std::endl; 389 | else if (map.type_.type == Type::count) 390 | std::cout << reduce_value(value, ncpus_) << std::endl; 391 | else 392 | std::cout << *(int64_t*)value.data() << std::endl; 393 | } 394 | 395 | std::cout << std::endl; 396 | 397 | return 0; 398 | } 399 | 400 | int BPFtrace::print_map_quantize(IMap &map) 401 | { 402 | // A quantize-map adds an extra 8 bytes onto the end of its key for storing 403 | // the bucket number. 404 | // e.g. A map defined as: @x[1, 2] = @quantize(3); 405 | // would actually be stored with the key: [1, 2, 3] 406 | 407 | std::vector old_key; 408 | try 409 | { 410 | old_key = find_empty_key(map, map.key_.size() + 8); 411 | } 412 | catch (std::runtime_error &e) 413 | { 414 | std::cerr << "Error getting key for map '" << map.name_ << "': " 415 | << e.what() << std::endl; 416 | return -2; 417 | } 418 | auto key(old_key); 419 | 420 | std::map, std::vector> values_by_key; 421 | 422 | while (bpf_get_next_key(map.mapfd_, old_key.data(), key.data()) == 0) 423 | { 424 | auto key_prefix = std::vector(map.key_.size()); 425 | int bucket = key.at(map.key_.size()); 426 | 427 | for (size_t i=0; i(value_size); 432 | int err = bpf_lookup_elem(map.mapfd_, key.data(), value.data()); 433 | if (err) 434 | { 435 | std::cerr << "Error looking up elem: " << err << std::endl; 436 | return -1; 437 | } 438 | 439 | if (values_by_key.find(key_prefix) == values_by_key.end()) 440 | { 441 | // New key - create a list of buckets for it 442 | values_by_key[key_prefix] = std::vector(65); 443 | } 444 | values_by_key[key_prefix].at(bucket) = reduce_value(value, ncpus_); 445 | 446 | old_key = key; 447 | } 448 | 449 | // Sort based on sum of counts in all buckets 450 | std::vector, uint64_t>> total_counts_by_key; 451 | for (auto &map_elem : values_by_key) 452 | { 453 | int sum = 0; 454 | for (size_t i=0; i &values) const 480 | { 481 | int max_index = -1; 482 | int max_value = 0; 483 | 484 | for (size_t i = 0; i < values.size(); i++) 485 | { 486 | int v = values.at(i); 487 | if (v != 0) 488 | max_index = i; 489 | if (v > max_value) 490 | max_value = v; 491 | } 492 | 493 | if (max_index == -1) 494 | return 0; 495 | 496 | for (int i = 0; i <= max_index; i++) 497 | { 498 | std::ostringstream header; 499 | if (i == 0) 500 | { 501 | header << "[0, 1]"; 502 | } 503 | else 504 | { 505 | header << "[" << quantize_index_label(i); 506 | header << ", " << quantize_index_label(i+1) << ")"; 507 | } 508 | 509 | int max_width = 52; 510 | int bar_width = values.at(i)/(float)max_value*max_width; 511 | std::string bar(bar_width, '@'); 512 | 513 | std::cout << std::setw(16) << std::left << header.str() 514 | << std::setw(8) << std::right << values.at(i) 515 | << " |" << std::setw(max_width) << std::left << bar << "|" 516 | << std::endl; 517 | } 518 | 519 | return 0; 520 | } 521 | 522 | std::string BPFtrace::quantize_index_label(int power) 523 | { 524 | char suffix = '\0'; 525 | if (power >= 40) 526 | { 527 | suffix = 'T'; 528 | power -= 40; 529 | } 530 | else if (power >= 30) 531 | { 532 | suffix = 'G'; 533 | power -= 30; 534 | } 535 | else if (power >= 20) 536 | { 537 | suffix = 'M'; 538 | power -= 20; 539 | } 540 | else if (power >= 10) 541 | { 542 | suffix = 'k'; 543 | power -= 10; 544 | } 545 | 546 | std::ostringstream label; 547 | label << (1< &value, int ncpus) 554 | { 555 | uint64_t sum = 0; 556 | for (int i=0; i BPFtrace::find_empty_key(IMap &map, size_t size) const 564 | { 565 | if (size == 0) size = 8; 566 | auto key = std::vector(size); 567 | int value_size = map.type_.size; 568 | if (map.type_.type == Type::count || map.type_.type == Type::quantize) 569 | value_size *= ncpus_; 570 | auto value = std::vector(value_size); 571 | 572 | if (bpf_lookup_elem(map.mapfd_, key.data(), value.data())) 573 | return key; 574 | 575 | for (auto &elem : key) elem = 0xff; 576 | if (bpf_lookup_elem(map.mapfd_, key.data(), value.data())) 577 | return key; 578 | 579 | for (auto &elem : key) elem = 0x55; 580 | if (bpf_lookup_elem(map.mapfd_, key.data(), value.data())) 581 | return key; 582 | 583 | throw std::runtime_error("Could not find empty key"); 584 | } 585 | 586 | std::string BPFtrace::get_stack(uint32_t stackid, bool ustack, int indent) 587 | { 588 | auto stack_trace = std::vector(MAX_STACK_SIZE); 589 | int err = bpf_lookup_elem(stackid_map_->mapfd_, &stackid, stack_trace.data()); 590 | if (err) 591 | { 592 | std::cerr << "Error looking up stack id " << stackid << ": " << err << std::endl; 593 | return ""; 594 | } 595 | 596 | std::ostringstream stack; 597 | std::string padding(indent, ' '); 598 | 599 | stack << "\n"; 600 | for (auto &addr : stack_trace) 601 | { 602 | if (addr == 0) 603 | break; 604 | if (!ustack) 605 | stack << padding << resolve_sym(addr, true) << std::endl; 606 | else 607 | stack << padding << resolve_usym(addr) << std::endl; 608 | } 609 | 610 | return stack.str(); 611 | } 612 | 613 | std::string BPFtrace::resolve_sym(uintptr_t addr, bool show_offset) 614 | { 615 | struct bcc_symbol sym; 616 | std::ostringstream symbol; 617 | 618 | if (ksyms_.resolve_addr(addr, &sym)) 619 | { 620 | symbol << sym.name; 621 | if (show_offset) 622 | symbol << "+" << sym.offset; 623 | } 624 | else 625 | { 626 | symbol << (void*)addr; 627 | } 628 | 629 | return symbol.str(); 630 | } 631 | 632 | std::string BPFtrace::resolve_usym(uintptr_t addr) const 633 | { 634 | // TODO 635 | std::ostringstream symbol; 636 | symbol << (void*)addr; 637 | return symbol.str(); 638 | } 639 | 640 | void BPFtrace::sort_by_key(std::vector key_args, 641 | std::vector, std::vector>> &values_by_key) 642 | { 643 | int arg_offset = 0; 644 | for (auto arg : key_args) 645 | { 646 | arg_offset += arg.size; 647 | } 648 | 649 | // Sort the key arguments in reverse order so the results are sorted by 650 | // the first argument first, then the second, etc. 651 | for (size_t i=key_args.size(); i-- > 0; ) 652 | { 653 | auto arg = key_args.at(i); 654 | arg_offset -= arg.size; 655 | 656 | if (arg.type == Type::integer) 657 | { 658 | if (arg.size == 8) 659 | { 660 | std::stable_sort(values_by_key.begin(), values_by_key.end(), [&](auto &a, auto &b) 661 | { 662 | return *(uint64_t*)(a.first.data() + arg_offset) < *(uint64_t*)(b.first.data() + arg_offset); 663 | }); 664 | } 665 | else 666 | abort(); 667 | } 668 | else if (arg.type == Type::string) 669 | { 670 | std::stable_sort(values_by_key.begin(), values_by_key.end(), [&](auto &a, auto &b) 671 | { 672 | return strncmp((char*)(a.first.data() + arg_offset), 673 | (char*)(b.first.data() + arg_offset), 674 | STRING_SIZE) < 0; 675 | }); 676 | } 677 | 678 | // Other types don't get sorted 679 | } 680 | } 681 | 682 | } // namespace bpftrace 683 | -------------------------------------------------------------------------------- /src/ast/semantic_analyser.cpp: -------------------------------------------------------------------------------- 1 | #include "semantic_analyser.h" 2 | #include "ast.h" 3 | #include "fake_map.h" 4 | #include "parser.tab.hh" 5 | #include "printf.h" 6 | #include "arch/arch.h" 7 | 8 | #include "libbpf.h" 9 | 10 | namespace bpftrace { 11 | namespace ast { 12 | 13 | void SemanticAnalyser::visit(Integer &integer) 14 | { 15 | integer.type = SizedType(Type::integer, 8); 16 | } 17 | 18 | void SemanticAnalyser::visit(String &string) 19 | { 20 | if (string.str.size() > STRING_SIZE-1) { 21 | err_ << "String is too long (over " << STRING_SIZE << " bytes): " << string.str << std::endl; 22 | } 23 | string.type = SizedType(Type::string, STRING_SIZE); 24 | } 25 | 26 | void SemanticAnalyser::visit(Builtin &builtin) 27 | { 28 | if (builtin.ident == "nsecs" || 29 | builtin.ident == "pid" || 30 | builtin.ident == "tid" || 31 | builtin.ident == "uid" || 32 | builtin.ident == "gid" || 33 | builtin.ident == "cpu" || 34 | builtin.ident == "retval") { 35 | builtin.type = SizedType(Type::integer, 8); 36 | } 37 | else if (builtin.ident == "stack") { 38 | builtin.type = SizedType(Type::stack, 8); 39 | needs_stackid_map_ = true; 40 | } 41 | else if (builtin.ident == "ustack") { 42 | builtin.type = SizedType(Type::ustack, 8); 43 | needs_stackid_map_ = true; 44 | } 45 | else if (builtin.ident == "comm") { 46 | builtin.type = SizedType(Type::string, STRING_SIZE); 47 | } 48 | else if (builtin.ident == "func") { 49 | for (auto &attach_point : *probe_->attach_points) 50 | { 51 | ProbeType type = probetype(attach_point->provider); 52 | if (type == ProbeType::kprobe || 53 | type == ProbeType::kretprobe || 54 | type == ProbeType::tracepoint) 55 | builtin.type = SizedType(Type::sym, 8); 56 | else if (type == ProbeType::uprobe || type == ProbeType::uretprobe) 57 | builtin.type = SizedType(Type::usym, 8); 58 | else 59 | err_ << "The func builtin can not be used with '" << attach_point->provider 60 | << "' probes" << std::endl; 61 | } 62 | } 63 | else if (!builtin.ident.compare(0, 3, "arg") && builtin.ident.size() == 4 && 64 | builtin.ident.at(3) >= '0' && builtin.ident.at(3) <= '9') { 65 | int arg_num = atoi(builtin.ident.substr(3).c_str()); 66 | if (arg_num > arch::max_arg()) 67 | err_ << arch::name() << " doesn't support " << builtin.ident << std::endl; 68 | builtin.type = SizedType(Type::integer, 8); 69 | } 70 | else { 71 | builtin.type = SizedType(Type::none, 0); 72 | err_ << "Unknown builtin variable: '" << builtin.ident << "'" << std::endl; 73 | } 74 | } 75 | 76 | void SemanticAnalyser::visit(Call &call) 77 | { 78 | if (call.vargs) { 79 | for (Expression *expr : *call.vargs) { 80 | expr->accept(*this); 81 | } 82 | } 83 | 84 | if (call.func == "quantize") { 85 | check_assignment(call, true, false); 86 | check_nargs(call, 1); 87 | check_arg(call, Type::integer, 0); 88 | 89 | call.type = SizedType(Type::quantize, 8); 90 | } 91 | else if (call.func == "count") { 92 | check_assignment(call, true, false); 93 | check_nargs(call, 0); 94 | 95 | call.type = SizedType(Type::count, 8); 96 | } 97 | else if (call.func == "delete") { 98 | check_assignment(call, false, false); 99 | if (check_nargs(call, 1)) { 100 | auto &arg = *call.vargs->at(0); 101 | if (!arg.is_map) 102 | err_ << "delete() expects a map to be provided" << std::endl; 103 | } 104 | 105 | call.type = SizedType(Type::none, 0); 106 | } 107 | else if (call.func == "str" || call.func == "sym" || call.func == "usym") { 108 | check_nargs(call, 1); 109 | check_arg(call, Type::integer, 0); 110 | 111 | if (call.func == "str") 112 | call.type = SizedType(Type::string, STRING_SIZE); 113 | else if (call.func == "sym") 114 | call.type = SizedType(Type::sym, 8); 115 | else if (call.func == "usym") 116 | call.type = SizedType(Type::usym, 8); 117 | } 118 | else if (call.func == "reg") { 119 | if (check_nargs(call, 1)) { 120 | if (check_arg(call, Type::string, 0, true)) { 121 | auto &arg = *call.vargs->at(0); 122 | auto ®_name = static_cast(arg).str; 123 | int offset = arch::offset(reg_name);; 124 | if (offset == -1) { 125 | err_ << "'" << reg_name << "' is not a valid register on this architecture"; 126 | err_ << " (" << arch::name() << ")" << std::endl; 127 | } 128 | } 129 | } 130 | 131 | call.type = SizedType(Type::integer, 8); 132 | } 133 | else if (call.func == "printf") { 134 | check_assignment(call, false, false); 135 | if (check_varargs(call, 1, 7)) { 136 | check_arg(call, Type::string, 0, true); 137 | if (is_final_pass()) { 138 | auto &fmt_arg = *call.vargs->at(0); 139 | String &fmt = static_cast(fmt_arg); 140 | std::vector args; 141 | for (auto iter = call.vargs->begin()+1; iter != call.vargs->end(); iter++) { 142 | args.push_back((*iter)->type); 143 | } 144 | err_ << verify_format_string(fmt.str, args); 145 | 146 | bpftrace_.printf_args_.push_back(std::make_tuple(fmt.str, args)); 147 | } 148 | } 149 | 150 | call.type = SizedType(Type::none, 0); 151 | } 152 | else { 153 | err_ << "Unknown function: '" << call.func << "'" << std::endl; 154 | call.type = SizedType(Type::none, 0); 155 | } 156 | } 157 | 158 | void SemanticAnalyser::visit(Map &map) 159 | { 160 | MapKey key; 161 | if (map.vargs) { 162 | for (Expression *expr : *map.vargs) { 163 | expr->accept(*this); 164 | key.args_.push_back({expr->type.type, expr->type.size}); 165 | } 166 | } 167 | 168 | auto search = map_key_.find(map.ident); 169 | if (search != map_key_.end()) { 170 | if (search->second != key) { 171 | err_ << "Argument mismatch for " << map.ident << ": "; 172 | err_ << "trying to access with arguments: "; 173 | err_ << key.argument_type_list(); 174 | err_ << "\n\twhen map expects arguments: "; 175 | err_ << search->second.argument_type_list(); 176 | err_ << "\n" << std::endl; 177 | } 178 | } 179 | else { 180 | map_key_.insert({map.ident, key}); 181 | } 182 | 183 | auto search_val = map_val_.find(map.ident); 184 | if (search_val != map_val_.end()) { 185 | map.type = search_val->second; 186 | } 187 | else { 188 | if (is_final_pass()) { 189 | err_ << "Undefined map: " << map.ident << std::endl; 190 | } 191 | map.type = SizedType(Type::none, 0); 192 | } 193 | } 194 | 195 | void SemanticAnalyser::visit(Variable &var) 196 | { 197 | auto search_val = variable_val_.find(var.ident); 198 | if (search_val != variable_val_.end()) { 199 | var.type = search_val->second; 200 | } 201 | else { 202 | err_ << "Undefined variable: " << var.ident << std::endl; 203 | var.type = SizedType(Type::none, 0); 204 | } 205 | } 206 | 207 | void SemanticAnalyser::visit(Binop &binop) 208 | { 209 | binop.left->accept(*this); 210 | binop.right->accept(*this); 211 | Type &lhs = binop.left->type.type; 212 | Type &rhs = binop.right->type.type; 213 | 214 | if (is_final_pass()) { 215 | if (lhs != rhs) { 216 | err_ << "Type mismatch for '" << opstr(binop) << "': "; 217 | err_ << "comparing '" << lhs << "' "; 218 | err_ << "with '" << rhs << "'" << std::endl; 219 | } 220 | else if (lhs != Type::integer && 221 | binop.op != Parser::token::EQ && 222 | binop.op != Parser::token::NE) { 223 | err_ << "The " << opstr(binop) << " operator can not be used on expressions of type " << lhs << std::endl; 224 | } 225 | } 226 | 227 | binop.type = SizedType(Type::integer, 8); 228 | } 229 | 230 | void SemanticAnalyser::visit(Unop &unop) 231 | { 232 | unop.expr->accept(*this); 233 | 234 | if (is_final_pass() && 235 | unop.expr->type.type != Type::integer && 236 | unop.expr->type.type != Type::cast) { 237 | err_ << "The " << opstr(unop) << " operator can not be used on expressions of type '" 238 | << unop.expr->type << "'" << std::endl; 239 | } 240 | 241 | if (unop.op == Parser::token::MUL && unop.expr->type.type == Type::cast) { 242 | std::string cast_type = unop.expr->type.cast_type; 243 | if (cast_type.back() == '*') { 244 | cast_type.pop_back(); 245 | unop.type = SizedType(Type::cast, 8, cast_type); 246 | } 247 | else { 248 | err_ << "Can not dereference struct/union of type '" << cast_type << "'. " 249 | << "It is not a pointer." << std::endl; 250 | } 251 | } 252 | else { 253 | unop.type = SizedType(Type::integer, 8); 254 | } 255 | } 256 | 257 | void SemanticAnalyser::visit(FieldAccess &acc) 258 | { 259 | acc.expr->accept(*this); 260 | 261 | if (acc.expr->type.type != Type::cast) { 262 | if (is_final_pass()) { 263 | err_ << "Can not access field '" << acc.field 264 | << "' on expression of type '" << acc.expr->type 265 | << "'" << std::endl; 266 | } 267 | return; 268 | } 269 | 270 | std::string cast_type = acc.expr->type.cast_type; 271 | if (cast_type.back() == '*') { 272 | err_ << "Can not access field '" << acc.field << "' on type '" 273 | << cast_type << "'. Try dereferencing it first, or using '->'" 274 | << std::endl; 275 | return; 276 | } 277 | 278 | auto fields = bpftrace_.structs_[cast_type].fields; 279 | if (fields.count(acc.field) == 0) { 280 | err_ << "Struct/union of type '" << cast_type << "' does not contain " 281 | << "a field named '" << acc.field << "'" << std::endl; 282 | } 283 | else { 284 | acc.type = fields[acc.field].type; 285 | } 286 | } 287 | 288 | void SemanticAnalyser::visit(Cast &cast) 289 | { 290 | cast.expr->accept(*this); 291 | 292 | std::string cast_type = cast.cast_type; 293 | if (cast_type.back() == '*') 294 | cast_type.pop_back(); 295 | if (bpftrace_.structs_.count(cast_type) == 0) { 296 | err_ << "Unknown struct/union: '" << cast_type << "'" << std::endl; 297 | return; 298 | } 299 | 300 | int cast_size; 301 | if (cast.cast_type.back() == '*') { 302 | cast_size = sizeof(uintptr_t); 303 | } 304 | else { 305 | cast_size = bpftrace_.structs_[cast.cast_type].size; 306 | } 307 | cast.type = SizedType(Type::cast, cast_size, cast.cast_type); 308 | } 309 | 310 | void SemanticAnalyser::visit(ExprStatement &expr) 311 | { 312 | expr.expr->accept(*this); 313 | } 314 | 315 | void SemanticAnalyser::visit(AssignMapStatement &assignment) 316 | { 317 | assignment.map->accept(*this); 318 | assignment.expr->accept(*this); 319 | 320 | std::string map_ident = assignment.map->ident; 321 | auto search = map_val_.find(map_ident); 322 | if (search != map_val_.end()) { 323 | if (search->second.type == Type::none) { 324 | if (is_final_pass()) { 325 | err_ << "Undefined map: " << map_ident << std::endl; 326 | } 327 | else { 328 | search->second = assignment.expr->type; 329 | } 330 | } 331 | else if (search->second.type != assignment.expr->type.type) { 332 | err_ << "Type mismatch for " << map_ident << ": "; 333 | err_ << "trying to assign value of type '" << assignment.expr->type; 334 | err_ << "'\n\twhen map already contains a value of type '"; 335 | err_ << search->second << "'\n" << std::endl; 336 | } 337 | } 338 | else { 339 | // This map hasn't been seen before 340 | map_val_.insert({map_ident, assignment.expr->type}); 341 | } 342 | 343 | if (assignment.expr->type.type == Type::cast) { 344 | std::string cast_type = assignment.expr->type.cast_type; 345 | std::string curr_cast_type = map_val_[map_ident].cast_type; 346 | if (curr_cast_type != "" && curr_cast_type != cast_type) { 347 | err_ << "Type mismatch for " << map_ident << ": "; 348 | err_ << "trying to assign value of type '" << cast_type; 349 | err_ << "'\n\twhen map already contains a value of type '"; 350 | err_ << curr_cast_type << "'\n" << std::endl; 351 | } 352 | else { 353 | map_val_[map_ident].cast_type = cast_type; 354 | } 355 | } 356 | } 357 | 358 | void SemanticAnalyser::visit(AssignVarStatement &assignment) 359 | { 360 | assignment.expr->accept(*this); 361 | 362 | std::string var_ident = assignment.var->ident; 363 | auto search = variable_val_.find(var_ident); 364 | if (search != variable_val_.end()) { 365 | if (search->second.type == Type::none) { 366 | if (is_final_pass()) { 367 | err_ << "Undefined variable: " << var_ident << std::endl; 368 | } 369 | else { 370 | search->second = assignment.expr->type; 371 | } 372 | } 373 | else if (search->second.type != assignment.expr->type.type) { 374 | err_ << "Type mismatch for " << var_ident << ": "; 375 | err_ << "trying to assign value of type '" << assignment.expr->type; 376 | err_ << "'\n\twhen variable already contains a value of type '"; 377 | err_ << search->second << "'\n" << std::endl; 378 | } 379 | } 380 | else { 381 | // This variable hasn't been seen before 382 | variable_val_.insert({var_ident, assignment.expr->type}); 383 | assignment.var->type = assignment.expr->type; 384 | } 385 | 386 | if (assignment.expr->type.type == Type::cast) { 387 | std::string cast_type = assignment.expr->type.cast_type; 388 | std::string curr_cast_type = variable_val_[var_ident].cast_type; 389 | if (curr_cast_type != "" && curr_cast_type != cast_type) { 390 | err_ << "Type mismatch for " << var_ident << ": "; 391 | err_ << "trying to assign value of type '" << cast_type; 392 | err_ << "'\n\twhen variable already contains a value of type '"; 393 | err_ << curr_cast_type << "'\n" << std::endl; 394 | } 395 | else { 396 | variable_val_[var_ident].cast_type = cast_type; 397 | } 398 | } 399 | } 400 | 401 | void SemanticAnalyser::visit(Predicate &pred) 402 | { 403 | pred.expr->accept(*this); 404 | if (is_final_pass() && pred.expr->type.type != Type::integer) { 405 | err_ << "Invalid type for predicate: " << pred.expr->type.type << std::endl; 406 | } 407 | } 408 | 409 | void SemanticAnalyser::visit(AttachPoint &ap) 410 | { 411 | if (ap.provider == "kprobe" || ap.provider == "kretprobe") { 412 | if (ap.target != "") 413 | err_ << "kprobes should not have a target" << std::endl; 414 | if (ap.func == "") 415 | err_ << "kprobes should be attached to a function" << std::endl; 416 | } 417 | else if (ap.provider == "uprobe" || ap.provider == "uretprobe") { 418 | if (ap.target == "") 419 | err_ << "uprobes should have a target" << std::endl; 420 | if (ap.func == "") 421 | err_ << "uprobes should be attached to a function" << std::endl; 422 | } 423 | else if (ap.provider == "tracepoint") { 424 | if (ap.target == "" || ap.func == "") 425 | err_ << "tracepoint probe must have a target" << std::endl; 426 | } 427 | else if (ap.provider == "profile") { 428 | if (ap.target == "") 429 | err_ << "profile probe must have unit of time" << std::endl; 430 | else if (ap.target != "hz" && 431 | ap.target != "us" && 432 | ap.target != "ms" && 433 | ap.target != "s") 434 | err_ << ap.target << " is not an accepted unit of time" << std::endl; 435 | if (ap.func != "") 436 | err_ << "profile probe must have an integer frequency" << std::endl; 437 | else if (ap.freq <= 0) 438 | err_ << "profile frequency should be a positive integer" << std::endl; 439 | } 440 | else if (ap.provider == "BEGIN" || ap.provider == "END") { 441 | if (ap.target != "" || ap.func != "") 442 | err_ << "BEGIN/END probes should not have a target" << std::endl; 443 | if (is_final_pass()) { 444 | if (ap.provider == "BEGIN") { 445 | if (has_begin_probe_) 446 | err_ << "More than one BEGIN probe defined" << std::endl; 447 | has_begin_probe_ = true; 448 | } 449 | if (ap.provider == "END") { 450 | if (has_end_probe_) 451 | err_ << "More than one END probe defined" << std::endl; 452 | has_end_probe_ = true; 453 | } 454 | } 455 | } 456 | else { 457 | err_ << "Invalid provider: '" << ap.provider << "'" << std::endl; 458 | } 459 | } 460 | 461 | void SemanticAnalyser::visit(Probe &probe) 462 | { 463 | // Clear out map of variable names - variables should be probe-local 464 | variable_val_.clear(); 465 | probe_ = &probe; 466 | 467 | for (AttachPoint *ap : *probe.attach_points) { 468 | ap->accept(*this); 469 | } 470 | if (probe.pred) { 471 | probe.pred->accept(*this); 472 | } 473 | for (Statement *stmt : *probe.stmts) { 474 | stmt->accept(*this); 475 | } 476 | 477 | if (is_final_pass()) { 478 | bpftrace_.add_probe(probe); 479 | } 480 | } 481 | 482 | void SemanticAnalyser::visit(Include &include) 483 | { 484 | } 485 | 486 | void SemanticAnalyser::visit(Program &program) 487 | { 488 | for (Include *include : *program.includes) 489 | include->accept(*this); 490 | for (Probe *probe : *program.probes) 491 | probe->accept(*this); 492 | } 493 | 494 | int SemanticAnalyser::analyse() 495 | { 496 | // Multiple passes to handle variables being used before they are defined 497 | std::string errors; 498 | 499 | for (pass_ = 1; pass_ <= num_passes_; pass_++) { 500 | root_->accept(*this); 501 | errors = err_.str(); 502 | if (!errors.empty()) { 503 | out_ << errors; 504 | return pass_; 505 | } 506 | } 507 | 508 | return 0; 509 | } 510 | 511 | int SemanticAnalyser::create_maps(bool debug) 512 | { 513 | for (auto &map_val : map_val_) 514 | { 515 | std::string map_name = map_val.first; 516 | SizedType type = map_val.second; 517 | 518 | auto search_args = map_key_.find(map_name); 519 | if (search_args == map_key_.end()) 520 | abort(); 521 | auto &key = search_args->second; 522 | 523 | if (debug) 524 | bpftrace_.maps_[map_name] = std::make_unique(map_name, type, key); 525 | else 526 | bpftrace_.maps_[map_name] = std::make_unique(map_name, type, key); 527 | } 528 | 529 | if (debug) 530 | { 531 | if (needs_stackid_map_) 532 | bpftrace_.stackid_map_ = std::make_unique(BPF_MAP_TYPE_STACK_TRACE); 533 | bpftrace_.perf_event_map_ = std::make_unique(BPF_MAP_TYPE_PERF_EVENT_ARRAY); 534 | } 535 | else 536 | { 537 | if (needs_stackid_map_) 538 | bpftrace_.stackid_map_ = std::make_unique(BPF_MAP_TYPE_STACK_TRACE); 539 | bpftrace_.perf_event_map_ = std::make_unique(BPF_MAP_TYPE_PERF_EVENT_ARRAY); 540 | } 541 | 542 | return 0; 543 | } 544 | 545 | bool SemanticAnalyser::is_final_pass() const 546 | { 547 | return pass_ == num_passes_; 548 | } 549 | 550 | bool SemanticAnalyser::check_assignment(const Call &call, bool want_map, bool want_var) 551 | { 552 | if (want_map && want_var) 553 | { 554 | if (!call.map && !call.var) 555 | { 556 | err_ << call.func << "() should be assigned to a map or a variable" << std::endl; 557 | return false; 558 | } 559 | } 560 | else if (want_map) 561 | { 562 | if (!call.map) 563 | { 564 | err_ << call.func << "() should be assigned to a map" << std::endl; 565 | return false; 566 | } 567 | } 568 | else if (want_var) 569 | { 570 | if (!call.var) 571 | { 572 | err_ << call.func << "() should be assigned to a variable" << std::endl; 573 | return false; 574 | } 575 | } 576 | else 577 | { 578 | if (call.map || call.var) 579 | { 580 | err_ << call.func << "() should not be used in an assignment" << std::endl; 581 | return false; 582 | } 583 | } 584 | return true; 585 | } 586 | 587 | bool SemanticAnalyser::check_nargs(const Call &call, int expected_nargs) 588 | { 589 | std::vector::size_type nargs = 0; 590 | if (call.vargs) 591 | nargs = call.vargs->size(); 592 | 593 | if (nargs != expected_nargs) 594 | { 595 | err_ << call.func << "() should take " << expected_nargs << " arguments ("; // TODO plural 596 | err_ << nargs << " provided)" << std::endl; 597 | return false; 598 | } 599 | return true; 600 | } 601 | 602 | bool SemanticAnalyser::check_varargs(const Call &call, int min_nargs, int max_nargs) 603 | { 604 | std::vector::size_type nargs = 0; 605 | if (call.vargs) 606 | nargs = call.vargs->size(); 607 | 608 | if (nargs < min_nargs) 609 | { 610 | err_ << call.func << "() requires at least " << min_nargs << " argument ("; // TODO plural 611 | err_ << nargs << " provided)" << std::endl; 612 | return false; 613 | } 614 | else if (nargs > max_nargs) 615 | { 616 | err_ << call.func << "() can only take up to " << max_nargs << " arguments ("; 617 | err_ << nargs << " provided)" << std::endl; 618 | return false; 619 | } 620 | return true; 621 | } 622 | 623 | bool SemanticAnalyser::check_arg(const Call &call, Type type, int arg_num, bool want_literal) 624 | { 625 | if (!call.vargs) 626 | return false; 627 | 628 | auto &arg = *call.vargs->at(arg_num); 629 | if (want_literal && (!arg.is_literal || arg.type.type != type)) 630 | { 631 | err_ << call.func << "() expects a " << type << " literal"; 632 | err_ << " (" << arg.type.type << " provided)" << std::endl; 633 | return false; 634 | } 635 | else if (is_final_pass() && arg.type.type != type) { 636 | err_ << call.func << "() only supports " << type << " arguments"; 637 | err_ << " (" << arg.type.type << " provided)" << std::endl; 638 | return false; 639 | } 640 | return true; 641 | } 642 | 643 | } // namespace ast 644 | } // namespace bpftrace 645 | --------------------------------------------------------------------------------