#include #include #include #include #include #include #include #include #include #include "bcc_common.h" #include "bpf_module.h" #include "libbpf.h" #include #include "table.h" #define TRACE_PIPE_PATH "/sys/kernel/debug/tracing/trace_pipe" #define DEBUGFS "/sys/kernel/debug" const std::vector syscall_prefixes = { "sys_", "__x64_sys_", "__x32_compat_sys_", "__ia32_compat_sys_", "__arm64_sys_", "__s390x_sys_", "__s390_sys_", "__riscv_sys_" }; class EbpfExtension : public Php::Base { private: ebpf::BPF bpf; void *mod; Php::Object _perf_event; PerfEventArrayTable *_class_perf_event; static std::string add_prefix(const std::string &prefix, const std::string &name) { if (name.rfind(prefix, 0) != 0) { return prefix + name; } return name; } std::string fix_syscall_fnname(const std::string &name) { for (const auto &prefix: syscall_prefixes) { if (name.rfind(prefix, 0) == 0) { return bpf.get_syscall_fnname(name.substr(prefix.length())); } } return name; } void _trace_autoload() { size_t num_funcs = bpf.get_num_functions(); for (size_t i = 0; i < num_funcs; i++) { const char *func_name = bpf.get_function_name(i); std::string fn_name(func_name); if (fn_name.rfind("kprobe__", 0) == 0) { std::string kernel_func = fix_syscall_fnname(fn_name.substr(8)); bpf.attach_kprobe(kernel_func, fn_name); } else if (fn_name.rfind("kretprobe__", 0) == 0) { std::string kernel_func = fix_syscall_fnname(fn_name.substr(11)); bpf.attach_kprobe(kernel_func, fn_name, 0, BPF_PROBE_RETURN); } else if (fn_name.rfind("tracepoint__", 0) == 0) { std::string tp_name = fn_name.substr(12); std::replace(tp_name.begin(), tp_name.end(), '_', ':'); bpf.attach_tracepoint(tp_name, fn_name); } else if (fn_name.rfind("raw_tracepoint__", 0) == 0) { std::string tp_name = fn_name.substr(16); bpf.attach_raw_tracepoint(tp_name, fn_name); } else if (fn_name.rfind("kfunc__", 0) == 0) { fn_name = add_prefix("kfunc__", fn_name); attach_kfunc(fn_name); } else if (fn_name.rfind("kretfunc__", 0) == 0) { fn_name = add_prefix("kretfunc__", fn_name); attach_kfunc(fn_name); } else if (fn_name.rfind("lsm__", 0) == 0) { fn_name = add_prefix("lsm__", fn_name); attach_lsm(fn_name); } } } public: EbpfExtension() = default; virtual ~EbpfExtension() = default; Php::Value __get(const Php::Value &name) { int from_attr = 1; auto res = get_table_cls(name, from_attr); if (!res.isObject()) { return Php::Base::__get(name); } else { return res; } } std::unordered_set get_kprobe_functions(const std::string &event_re) { std::unordered_set blacklist; std::unordered_set avail_filter; std::unordered_set fns; std::string blacklist_file = std::string(DEBUGFS) + "/kprobes/blacklist"; std::ifstream blacklist_f(blacklist_file); if (blacklist_f.is_open()) { std::string line; while (std::getline(blacklist_f, line)) { std::istringstream iss(line); std::string addr, func_name; if (iss >> addr >> func_name) { blacklist.insert(func_name); } } blacklist_f.close(); } std::string avail_filter_file = std::string(DEBUGFS) + "/tracing/available_filter_functions"; std::ifstream avail_filter_f(avail_filter_file); if (avail_filter_f.is_open()) { std::string line; while (std::getline(avail_filter_f, line)) { std::istringstream iss(line); std::string func_name; if (iss >> func_name) { avail_filter.insert(func_name); } } avail_filter_f.close(); } std::ifstream kallsyms_f("/proc/kallsyms"); if (!kallsyms_f.is_open()) { std::cerr << "Failed to open /proc/kallsyms\n"; return fns; } std::string line; bool in_init_section = false; bool in_irq_section = false; std::regex cold_regex(".*\\.cold(\\.\\d+)?$"); while (std::getline(kallsyms_f, line)) { std::istringstream iss(line); std::string addr, type, func_name; if (!(iss >> addr >> type >> func_name)) { continue; } if (!in_init_section) { if (func_name == "__init_begin") { in_init_section = true; continue; } } else if (func_name == "__init_end") { in_init_section = false; continue; } if (!in_irq_section) { if (func_name == "__irqentry_text_start") { in_irq_section = true; continue; } else if (func_name == "__irqentry_text_end") { in_irq_section = false; continue; } } else if (func_name == "__irqentry_text_end") { in_irq_section = false; continue; } // 过滤掉 NOKPROBE_SYMBOL() 定义的 _kbl_addr_* if (func_name.rfind("_kbl_addr_", 0) == 0) { continue; } // 过滤掉 perf 相关函数 if (func_name.rfind("__perf", 0) == 0 || func_name.rfind("perf_", 0) == 0) { continue; } // 过滤掉 __SCT__ 前缀的静态函数 if (func_name.rfind("__SCT__", 0) == 0) { continue; } // 过滤掉 GCC 8 编译生成的 .cold 函数 if (std::regex_match(func_name, cold_regex)) { continue; } if ((type == "t" || type == "T" || type == "w" || type == "W") && func_name == event_re && blacklist.find(func_name) == blacklist.end() && avail_filter.find(func_name) != avail_filter.end()) { fns.insert(func_name); } } return fns; } Php::Value php_get_kprobe_functions(Php::Parameters ¶ms) { std::string fn = params[0].stringValue(); auto res = get_kprobe_functions(fn); Php::Array result; for (const auto &item: res) { result[result.size()] = item; // 将每个匹配的函数名添加到 Php::Array } return result; } Php::Value php_test(Php::Parameters ¶ms) { std::string fn = params[0].stringValue(); Php::out << fn << std::endl; auto res = get_kprobe_functions(fn); Php::Array result; for (const auto &item : res) { result[result.size()] = item; // 将每个匹配的函数名添加到 Php::Array } return result; } void __construct(Php::Parameters ¶ms) { std::string bpf_code = params[0].stringValue(); auto init_res = bpf.init(bpf_code); if (!init_res.ok()) { throw Php::Exception("init error: " + init_res.msg()); } this->mod = (void *) bpf.get_mod(); _trace_autoload(); } ebpf::StatusTuple attach_kfunc(const std::string &kfn) { int probe_fd; auto fn = bpf.load_func(kfn, BPF_PROG_TYPE_TRACING, probe_fd); int res_fd = bpf_attach_kfunc(probe_fd); if (res_fd < 0) { TRY2(bpf.unload_func(kfn)); return ebpf::StatusTuple(-1, "Unable to attach kfunc using %s", kfn.c_str()); } return ebpf::StatusTuple::OK(); } ebpf::StatusTuple attach_lsm(const std::string &lsm) { int probe_fd; auto fn = bpf.load_func(lsm, BPF_PROG_TYPE_LSM, probe_fd); int res_fd = bpf_attach_lsm(probe_fd); if (res_fd < 0) { TRY2(bpf.unload_func(lsm)); return ebpf::StatusTuple(-1, "Unable to attach lsm using %s", lsm.c_str()); } return ebpf::StatusTuple::OK(); } void php_attach_kprobe(Php::Parameters ¶ms) { std::string kernel_func = params[0].stringValue(); std::string probe_func = params[1].stringValue(); auto attach_res = bpf.attach_kprobe(kernel_func, probe_func); if (!attach_res.ok()) { throw Php::Exception("attach error: " + attach_res.msg()); } } void php_attach_tracepoint(Php::Parameters ¶ms) { std::string tp_func = params[0].stringValue(); std::string probe_func = params[1].stringValue(); auto attach_res = bpf.attach_tracepoint(tp_func, probe_func); if (!attach_res.ok()) { throw Php::Exception("attach error: " + attach_res.msg()); } } void php_attach_raw_tracepoint(Php::Parameters ¶ms) { std::string tp_func = params[0].stringValue(); std::string probe_func = params[1].stringValue(); auto attach_res = bpf.attach_raw_tracepoint(tp_func, probe_func); if (!attach_res.ok()) { throw Php::Exception("attach error: " + attach_res.msg()); } } void php_attach_kfunc(Php::Parameters ¶ms) { std::string kfunc = params[0].stringValue(); auto attach_res = attach_kfunc(kfunc); if (!attach_res.ok()) { throw Php::Exception("attach error: " + attach_res.msg()); } } void php_attach_lsm(Php::Parameters ¶ms) { std::string kfunc = params[0].stringValue(); auto attach_res = attach_lsm(kfunc); if (!attach_res.ok()) { throw Php::Exception("attach error: " + attach_res.msg()); } } void php_attach_uprobe(Php::Parameters ¶ms) { std::string binary_path = params[0].stringValue(); std::string symbol = params[1].stringValue(); std::string probe_func = params[2].stringValue(); int64_t symbol_addr = 0, symbol_offset = 0, pid_param; uint32_t ref_ctr_offset = 0; pid_t pid = -1; if (params.size() > 3) { Php::Value options = params[3]; symbol_addr = options.get("symbol_addr").numericValue(); symbol_offset = options.get("symbol_offset").numericValue(); ref_ctr_offset = options.get("ref_ctr_offset").numericValue(); pid_param = options.get("pid").numericValue(); if (pid_param > 0) { pid = static_cast(pid_param); } } auto attach_res = bpf.attach_uprobe(binary_path, symbol, probe_func, symbol_addr, BPF_PROBE_ENTRY, pid, symbol_offset, ref_ctr_offset); if (!attach_res.ok()) { throw Php::Exception("attach error: " + attach_res.msg()); } } void php_detach_kprobe(Php::Parameters ¶ms) { std::string fn = params[0].stringValue(); auto detach_res = bpf.detach_kprobe(fn); if (!detach_res.ok()) { throw Php::Exception("detach_kprobe error: " + detach_res.msg()); } } void php_detach_uprobe(Php::Parameters ¶ms) { std::string binary_path = params[0].stringValue(); std::string symbol = params[1].stringValue(); int64_t symbol_addr = 0, symbol_offset = 0, pid_param; pid_t pid = -1; if (params.size() > 2) { Php::Value options = params[2]; symbol_addr = options.get("symbol_addr").numericValue(); symbol_offset = options.get("symbol_offset").numericValue(); pid_param = options.get("pid").numericValue(); if (pid_param > 0) { pid = static_cast(pid_param); } } auto detach_res = bpf.detach_uprobe(binary_path, symbol, symbol_addr, BPF_PROBE_ENTRY, pid, symbol_offset); if (!detach_res.ok()) { throw Php::Exception("detach_kprobe error: " + detach_res.msg()); } } [[noreturn]] static void php_trace_print() { std::ifstream pipe(TRACE_PIPE_PATH); std::string line; if (!pipe.is_open()) { throw Php::Exception("Failed to open trace_pipe"); } std::cout << "Press Ctrl+C to stop..." << std::endl; while (true) { if (std::getline(pipe, line)) { std::cout << line << std::endl; } } } static Php::Value php_trace_fields(Php::Parameters ¶ms) { std::ifstream traceFile(TRACE_PIPE_PATH); if (!traceFile.is_open()) { throw Php::Exception("Failed to open trace_pipe"); } std::string line; while (std::getline(traceFile, line)) { if (line.empty()) { continue; } if (line.rfind("CPU:", 0) == 0) { continue; } std::string task = line.substr(0, 16); task.erase(0, task.find_first_not_of(" ")); std::istringstream iss(line.substr(17)); std::string pid, cpu, flags, ts, msg; char delim; if (!(iss >> pid >> delim >> cpu >> delim >> flags >> ts)) { continue; } size_t sym_end = iss.str().find(": ", iss.tellg()); if (sym_end != std::string::npos) { msg = iss.str().substr(sym_end + 2); } Php::Array result; result[0] = task; result[1] = std::stoi(pid); result[2] = std::stoi(cpu.substr(1, cpu.size() - 2)); // 去掉方括号 result[3] = flags; result[4] = std::stod(ts); result[5] = msg; return result; } return Php::Value(); } Php::Value php_perf_event(Php::Parameters ¶ms) { std::string event_name = params[0].stringValue(); // Get event_name from the parameters this->_class_perf_event = new PerfEventArrayTable(&this->bpf, event_name); return Php::Object("PerfEventArrayTable", this->_class_perf_event); } Php::Value get_table_cls(const char *table_name, int from_attr) { int ttype = bpf_table_type(this->mod, table_name); switch (ttype) { case BPF_MAP_TYPE_HASH: return Php::Object("HashTable", new HashTable(&this->bpf, table_name)); case BPF_MAP_TYPE_ARRAY: return Php::Object("ArrayTable", new ArrayTable(&this->bpf, table_name)); case BPF_MAP_TYPE_PROG_ARRAY: return Php::Object("ProgArrayTable", new ProgArrayTable(&this->bpf, table_name)); case BPF_MAP_TYPE_PERF_EVENT_ARRAY: this->_class_perf_event = new PerfEventArrayTable(&this->bpf, table_name); return Php::Object("PerfEventArrayTable", this->_class_perf_event); case BPF_MAP_TYPE_PERCPU_HASH: return Php::Object("PerCpuHashTable", new PerCpuHashTable(&this->bpf, table_name)); case BPF_MAP_TYPE_PERCPU_ARRAY: return Php::Object("PerCpuArrayTable", new PerCpuArrayTable(&this->bpf, table_name)); case BPF_MAP_TYPE_LPM_TRIE: return Php::Object("LpmTrieTable", new LpmTrieTable(&this->bpf, table_name)); case BPF_MAP_TYPE_STACK_TRACE: return Php::Object("StackTraceTable", new StackTraceTable(&this->bpf, table_name)); case BPF_MAP_TYPE_LRU_HASH: return Php::Object("LruHashTable", new LruHashTable(&this->bpf, table_name)); case BPF_MAP_TYPE_LRU_PERCPU_HASH: return Php::Object("LruPerCpuHashTable", new LruPerCpuHashTable(&this->bpf, table_name)); case BPF_MAP_TYPE_CGROUP_ARRAY: return Php::Object("CgroupArrayTable", new CgroupArrayTable(&this->bpf, table_name)); case BPF_MAP_TYPE_DEVMAP: return Php::Object("DevMapTable", new DevMapTable(&this->bpf, table_name)); case BPF_MAP_TYPE_CPUMAP: return Php::Object("CpuMapTable", new CpuMapTable(&this->bpf, table_name)); case BPF_MAP_TYPE_XSKMAP: return Php::Object("XskMapTable", new XskMapTable(&this->bpf, table_name)); case BPF_MAP_TYPE_ARRAY_OF_MAPS: return Php::Object("MapInMapArrayTable", new MapInMapArrayTable(&this->bpf, table_name)); case BPF_MAP_TYPE_HASH_OF_MAPS: return Php::Object("MapInMapHashTable", new MapInMapHashTable(&this->bpf, table_name)); case BPF_MAP_TYPE_QUEUE: case BPF_MAP_TYPE_STACK: return Php::Object("QueueStackTable", new QueueStackTable(&this->bpf, table_name)); case BPF_MAP_TYPE_RINGBUF: return Php::Object("RingBufTable", new RingBufTable(&this->bpf, table_name)); default: if (from_attr) { return ttype; } throw Php::Exception("Unknown table type " + std::to_string(ttype)); } } Php::Value php_get_table(Php::Parameters ¶ms) { std::string table_name = params[0].stringValue(); int from_fn = 0; return get_table_cls(table_name.c_str(), from_fn); } void php_perf_buffer_poll(Php::Parameters ¶ms) { if (!this->_class_perf_event) { throw Php::Exception("perf event is null."); } int timeout_ms = -1; int res = this->_class_perf_event->perf_buffer_poll(timeout_ms); if (res < 0) { throw Php::Exception("perf buffer poll error."); } } Php::Value php_get_syscall_fnname(Php::Parameters ¶ms) { std::string name = params[0].stringValue(); return bpf.get_syscall_fnname(name); } }; std::string sanitize_str(std::string str, bool (*validator)(char), char replacement = '_') { for (size_t i = 0; i < str.length(); i++) if (!validator(str[i])) str[i] = replacement; return str; } std::string attach_type_prefix(bpf_probe_attach_type type) { switch (type) { case BPF_PROBE_ENTRY: return "p"; case BPF_PROBE_RETURN: return "r"; } return "ERROR"; } static bool kprobe_event_validator(char c) { return (c != '+') && (c != '.'); } std::string get_kprobe_event(const std::string &kernel_func, bpf_probe_attach_type type) { std::string res = attach_type_prefix(type) + "_"; res += sanitize_str(kernel_func, &kprobe_event_validator); return res; } extern "C" { PHPCPP_EXPORT void *get_module() { static Php::Extension extension("ebpf", "1.0.0"); Php::Class ebpf_class("Ebpf"); ebpf_class.method<&EbpfExtension::php_trace_print>("trace_print"); ebpf_class.method<&EbpfExtension::php_trace_fields>("trace_fields"); ebpf_class.method<&EbpfExtension::__construct>("__construct", { Php::ByVal("bpf_code", Php::Type::String) }); ebpf_class.method<&EbpfExtension::php_attach_kprobe>("attach_kprobe", { Php::ByVal("kernel_func", Php::Type::String), Php::ByVal("probe_func", Php::Type::String) }); ebpf_class.method<&EbpfExtension::php_attach_tracepoint>("attach_tracepoint", { Php::ByVal("tp", Php::Type::String), Php::ByVal("probe_func", Php::Type::String) }); ebpf_class.method<&EbpfExtension::php_attach_raw_tracepoint>("attach_raw_tracepoint", { Php::ByVal("tp", Php::Type::String), Php::ByVal("probe_func", Php::Type::String) }); ebpf_class.method<&EbpfExtension::php_attach_kfunc>("attach_kfunc", { Php::ByVal("kfn", Php::Type::String), }); ebpf_class.method<&EbpfExtension::php_attach_lsm>("attach_lsm", { Php::ByVal("lsm", Php::Type::String), }); ebpf_class.method<&EbpfExtension::php_attach_uprobe>("attach_uprobe", { Php::ByVal("binary_path", Php::Type::String), Php::ByVal("symbol", Php::Type::String), Php::ByVal("probe_func", Php::Type::String), Php::ByVal("options", Php::Type::Array, false), }); // ebpf_class.method<&EbpfExtension::php_open_perf_buffer>("open_perf_buffer", { // Php::ByVal("callback", Php::Type::Callable), // }); ebpf_class.method<&EbpfExtension::php_perf_event>("perf_event", { Php::ByVal("ev_name", Php::Type::String), }); ebpf_class.method<&EbpfExtension::php_get_table>("get_table", { Php::ByVal("tb_name", Php::Type::String), }); ebpf_class.method<&EbpfExtension::php_perf_buffer_poll>("perf_buffer_poll", {}); ebpf_class.method<&EbpfExtension::php_get_syscall_fnname>("get_syscall_fnname", { Php::ByVal("name", Php::Type::String), }); /*detach*/ ebpf_class.method<&EbpfExtension::php_detach_kprobe>("detach_kprobe", { Php::ByVal("kernel_func", Php::Type::String), }); ebpf_class.method<&EbpfExtension::php_detach_uprobe>("detach_uprobe", { Php::ByVal("binary_path", Php::Type::String), Php::ByVal("symbol", Php::Type::String), Php::ByVal("options", Php::Type::Array, false), }); ebpf_class.method<&EbpfExtension::php_test>("test", { Php::ByVal("idx", Php::Type::String), }); ebpf_class.method<&EbpfExtension::php_get_kprobe_functions>("get_kprobe_functions", { Php::ByVal("fn", Php::Type::String), }); extension.add(std::move(ebpf_class)); /*HashTable*/ Php::Class ebpf_hashtable_cls("HashTable"); ebpf_hashtable_cls.method<&HashTable::php_get_values>("values", { }); /*ArrayTable*/ Php::Class ebpf_array_table_cls("ArrayTable"); ebpf_array_table_cls.method<&ArrayTable::php_get_value>("get_value", { Php::ByVal("idx", Php::Type::Numeric), }); /*ProgArrayTable*/ Php::Class ebpf_prog_array_table_cls("ProgArrayTable"); /*PerfEventArrayTable*/ Php::Class ebpf_perf_event_class("PerfEventArrayTable"); ebpf_perf_event_class.method<&PerfEventArrayTable::php_open_perf_buffer>("open_perf_buffer", { Php::ByVal("fn", Php::Type::String), }); /*PerCpuHashTable*/ Php::Class ebpf_percpuhash_table_cls("PerCpuHashTable"); /*PerCpuArrayTable*/ Php::Class ebpf_per_cpu_array_table_cls("PerCpuArrayTable"); ebpf_per_cpu_array_table_cls.method<&PerCpuArrayTable::php_sum_value>("sum_value", { Php::ByVal("idx", Php::Type::Numeric), }); /*LpmTrieTable*/ Php::Class ebpf_lpmtrie_table_cls("LpmTrieTable"); /*StackTraceTable*/ Php::Class ebpf_stack_trace_table_cls("StackTraceTable"); ebpf_stack_trace_table_cls.method<&StackTraceTable::php_get_values>("values", { Php::ByVal("stack", Php::Type::Numeric), Php::ByVal("pid", Php::Type::Numeric), }); /*LruHashTable*/ Php::Class ebpf_lruhash_table_cls("LruHashTable"); /*LruPerCpuHashTable*/ Php::Class ebpf_lruper_cpuhash_table_cls("LruPerCpuHashTable"); /*CgroupArrayTable*/ Php::Class ebpf_cgroup_array_table_cls("CgroupArrayTable"); /*DevMapTable*/ Php::Class ebpf_devmap_table_cls("DevMapTable"); /*CpuMapTable*/ Php::Class ebpf_cpumap_table_cls("CpuMapTable"); /*XskMapTable*/ Php::Class ebpf_xskmap_table_cls("XskMapTable"); /*MapInMapArrayTable*/ Php::Class ebpf_map_in_map_array_table_cls("MapInMapArrayTable"); /*MapInMapHashTable*/ Php::Class ebpf_map_in_maphash_table_cls("MapInMapHashTable"); /*QueueStackTable*/ Php::Class ebpf_queue_stack_table_cls("QueueStackTable"); /*RingBufTable*/ Php::Class ebpf_ringbuf_table_cls("RingBufTable"); extension.add(std::move(ebpf_hashtable_cls)); extension.add(std::move(ebpf_array_table_cls)); extension.add(std::move(ebpf_prog_array_table_cls)); extension.add(std::move(ebpf_perf_event_class)); extension.add(std::move(ebpf_percpuhash_table_cls)); extension.add(std::move(ebpf_per_cpu_array_table_cls)); extension.add(std::move(ebpf_lpmtrie_table_cls)); extension.add(std::move(ebpf_stack_trace_table_cls)); extension.add(std::move(ebpf_lruhash_table_cls)); extension.add(std::move(ebpf_lruper_cpuhash_table_cls)); extension.add(std::move(ebpf_cgroup_array_table_cls)); extension.add(std::move(ebpf_devmap_table_cls)); extension.add(std::move(ebpf_cpumap_table_cls)); extension.add(std::move(ebpf_xskmap_table_cls)); extension.add(std::move(ebpf_map_in_map_array_table_cls)); extension.add(std::move(ebpf_map_in_maphash_table_cls)); extension.add(std::move(ebpf_queue_stack_table_cls)); extension.add(std::move(ebpf_ringbuf_table_cls)); return extension; } }