Carl hace 10 meses
commit
538ba5e592
Se han modificado 4 ficheros con 658 adiciones y 0 borrados
  1. 3 0
      .gitignore
  2. 46 0
      Makefile
  3. 567 0
      main.cpp
  4. 42 0
      test.php

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+*.o
+*.so
+.idea

+ 46 - 0
Makefile

@@ -0,0 +1,46 @@
+NAME				=	ebpf
+INI_DIR				=	/etc/php/7.4/cli/conf.d/
+EXTENSION_DIR		=	$(shell php-config --extension-dir)
+EXTENSION 			=	${NAME}.so
+INI 				=	${NAME}.ini
+
+COMPILER			=	g++
+LINKER				=	g++
+LIB_BCC = /opt/github/bcc/src
+COMPILER_FLAGS		=	-Wall -c -O2 -fpic -I${LIB_BCC}/cc -I${LIB_BCC}/cc/api -I${LIB_BCC}/cc/frontends/clang -I/usr/lib/llvm-14/include -DKERNEL_MODULES_DIR=\"/lib/modules/5.15.0-130-generic\" -o
+LINKER_FLAGS		=	-shared
+LINKER_DEPENDENCIES	=	-lphpcpp -lbcc
+
+
+
+RM					=	rm -f
+CP					=	cp -f
+MKDIR				=	mkdir -p
+
+SOURCES				=	$(wildcard *.cpp)
+OBJECTS				=	$(SOURCES:%.cpp=%.o)
+
+
+#
+#	From here the build instructions start
+#
+
+BCC_SHARED_DIR=/opt/github/bcc/build/src/cc/CMakeFiles/bcc-shared.dir
+BCC1 =  ${BCC_SHARED_DIR}/*.o
+
+
+all:					${OBJECTS} ${EXTENSION}
+
+${EXTENSION}:			${OBJECTS}
+						${LINKER} ${LINKER_FLAGS} -o $@ ${OBJECTS} ${BCC} ${LINKER_DEPENDENCIES}
+
+${OBJECTS}:
+						${COMPILER} ${COMPILER_FLAGS} $@ ${@:%.o=%.cpp}
+
+install:		
+						${CP} ${EXTENSION} ${EXTENSION_DIR}
+						${CP} ${INI} ${INI_DIR}
+				
+clean:
+						${RM} ${EXTENSION} ${OBJECTS}
+

+ 567 - 0
main.cpp

@@ -0,0 +1,567 @@
+#include <iostream>
+#include <phpcpp.h>
+#include <fstream>
+#include "bcc_common.h"
+#include "bpf_module.h"
+#include "libbpf.h"
+#include "BPF.h"
+#include <csignal>
+#include <atomic>
+#include <sstream>
+#include <utility>
+#include <vector>
+
+constexpr const char *TRACE_PIPE_PATH = "/sys/kernel/debug/tracing/trace_pipe";
+
+// Php::out << "Value of fn: " << fnn << std::endl;
+//static std::string cb_fn;
+
+const std::vector<std::string> syscall_prefixes = {
+		"sys_",
+		"__x64_sys_",
+		"__x32_compat_sys_",
+		"__ia32_compat_sys_",
+		"__arm64_sys_",
+		"__s390x_sys_",
+		"__s390_sys_",
+		"__riscv_sys_"
+};
+
+
+class PerfEvent : public Php::Base {
+private:
+	std::string cb;
+public:
+	ebpf::BPF *bpf;
+	std::string event_name;
+
+	PerfEvent(ebpf::BPF *bpf_instance, const std::string &event_name)
+			: bpf(bpf_instance), event_name(event_name) {}
+
+	virtual ~PerfEvent() = default;
+
+	static void callbackfn(void *cookie, void *data, int data_size) {
+		auto *instance = static_cast<PerfEvent *>(cookie);
+		if (!instance) return;
+
+		Php::Value phpData((const char *) data, data_size);
+		Php::call(instance->cb.c_str(), nullptr, phpData, data_size);
+	}
+
+	void php_open_perf_buffer(Php::Parameters &params) {
+		this->cb = params[0].stringValue();
+		auto res = this->bpf->open_perf_buffer(this->event_name, callbackfn, nullptr, this);
+		if (!res.ok()) {
+			throw Php::Exception("open_perf_buffer error:" + res.msg());
+		}
+	}
+
+	int perf_buffer_poll(int timeout_ms) {
+		return bpf->poll_perf_buffer(this->event_name, timeout_ms); // perf_reader_event_read
+	}
+
+};
+
+
+class EbpfExtension : public Php::Base {
+private:
+	ebpf::BPF bpf;
+	Php::Object _perf_event;
+	PerfEvent *_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)
+//	{
+//		if (name == "events") return _perf_event;
+//
+//		auto a = new PerfEvent;
+//		a->getMessage();
+//
+//		return Php::Base::__get(name);
+//	}
+
+	void __construct(Php::Parameters &params) {
+		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());
+		}
+		_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 &params) {
+		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 &params) {
+		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 &params) {
+		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 &params) {
+		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 &params) {
+		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 &params) {
+		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_t>(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 &params) {
+		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 &params) {
+		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_t>(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 &params) {
+		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 &params) {
+		std::string event_name = params[0].stringValue();  // Get event_name from the parameters
+		this->_class_perf_event = new PerfEvent(&this->bpf, event_name);
+		return Php::Object("PerfEvent", this->_class_perf_event);
+	}
+
+	void php_perf_buffer_poll(Php::Parameters &params) {
+
+		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 &params) {
+		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;
+}
+
+
+void bpf_new2(Php::Parameters &params) {
+	std::string param = params[0].stringValue();
+
+	ebpf::BPF bpf;
+//	void *mod = bpf_module_create_c_from_string(param.c_str(), 4, NULL, 0, true, NULL);
+	std::string kernel_func = bpf.get_syscall_fnname("clone");
+
+	bpf.init(param);
+	const char *prob_fn = "kprobe__sys_clone";
+	auto attach_res = bpf.attach_kprobe(kernel_func, prob_fn);
+	Php::out << "Value of fn:kernel_func]" << kernel_func.c_str() << std::endl;
+	Php::out << "Value of fn:prob_fn]" << prob_fn << std::endl;
+
+	std::ifstream pipe("/sys/kernel/debug/tracing/trace_pipe");
+	std::string line;
+	if (!attach_res.ok()) {
+		std::cerr << attach_res.msg() << std::endl;
+	} else {
+		std::cout << "Press Ctrl+C to stop..." << std::endl;
+		while (true) {
+			if (std::getline(pipe, line)) {
+				std::cout << line << std::endl;
+			}
+		}
+	}
+}
+
+void bpf_new(Php::Parameters &params) {
+	std::string param = params[0].stringValue();
+	void *mod = bpf_module_create_c_from_string(param.c_str(), 4, NULL, 0, true, NULL);
+//	void *mod = bpf_module_create_c_from_string("BPF_TABLE(\"array\", int, int, stats, 10);\n", 4, NULL, 0, true, NULL);
+//	bpf_prog_get_fd_by_id()	;
+	Php::out << "Value of mod: " << mod << std::endl;
+
+//	size_t num = bpf_num_functions(mod);
+//	for (size_t i = 0; i < num; ++i) {
+//		const char *fnn = bpf_function_name(mod, i);
+//		Php::out << "Value of fn: " << fnn << std::endl;
+//	}
+
+//	Php::out << "Value of bpf_num_functions: " << num << std::endl;
+	ebpf::BPF bpf;
+	std::string kernel_func = bpf.get_syscall_fnname("clone");
+
+	const char *prob_fn = "hello";
+	void *func_start = bpf_function_start(mod, prob_fn);
+	if (!func_start) {
+		Php::out << "can not find of func_start: " << func_start << std::endl;
+	}
+	int log_level = 1;
+	std::cout << "func_load ]" << prob_fn << std::endl;
+	std::cout << "func_load func_start]" << func_start << std::endl;
+	std::cout << "func_load func_size]" << bpf_function_size(mod, prob_fn) << std::endl;
+	std::cout << "func_load license]" << bpf_module_license(mod) << std::endl;
+	std::cout << "func_load kern_version]" << bpf_module_kern_version(mod) << std::endl;
+	int fn_fd = bcc_func_load(mod, BPF_PROG_TYPE_KPROBE, prob_fn,
+	                          static_cast<const bpf_insn *>(func_start),
+	                          static_cast<int>(bpf_function_size(mod, prob_fn)),
+	                          bpf_module_license(mod),
+	                          bpf_module_kern_version(mod),
+	                          log_level, nullptr, 0, 0, -1);
+	if (fn_fd < 0) {
+		Php::out << "Failed to load BPF program " << fn_fd << std::endl;
+	} else {
+		Php::out << "BPF program fd ]" << fn_fd << std::endl;
+	}
+
+
+	std::string probe_event = get_kprobe_event(kernel_func, BPF_PROBE_ENTRY);
+
+	std::cout << "kernel_func]" << kernel_func.c_str() << "! probe_event]" << probe_event.c_str() << std::endl;
+	int kernel_func_offset = 0;
+//	bpf_attach_kprobe(fn_fd, BPF_PROBE_ENTRY, probe_event.c_str(), fn, fn_offset, 0);
+	int attach_res2 = bpf_attach_kprobe(fn_fd, BPF_PROBE_ENTRY, probe_event.c_str(), kernel_func.c_str(),
+	                                    kernel_func_offset, 0);
+
+	std::cout << "main.cpp attach_res2---------->]" << attach_res2 << std::endl;
+
+
+
+//	Php::out << "Value of clone_fnname: " << kernel_func << std::endl;
+	std::ifstream pipe("/sys/kernel/debug/tracing/trace_pipe");
+	std::string line;
+//
+//	bpf.init(param);
+
+	auto attach_res = bpf.attach_kprobe(kernel_func, prob_fn);
+	std::cout << "main.cpp attach_res2-------->]" << attach_res.ok() << std::endl;
+
+	if (!attach_res.ok() && attach_res2 <= 0) {
+		std::cerr << attach_res.msg() << std::endl;
+	} else {
+		std::cout << "Starting HelloWorld with BCC " << 111 << std::endl;
+		while (true) {
+			if (std::getline(pipe, line)) {
+				std::cout << "aaa Waiting for a sys_clone event" << std::endl;
+				std::cout << line << std::endl;
+				// Detach the probe if we got at least one line.
+				auto detach_res = bpf.detach_kprobe(kernel_func);
+				if (!detach_res.ok()) {
+					std::cerr << detach_res.msg() << std::endl;
+				}
+				break;
+			} else {
+				std::cout << "Waiting for a sys_clone event" << std::endl;
+				sleep(1);
+			}
+		}
+	}
+}
+
+extern "C" {
+PHPCPP_EXPORT void *get_module() {
+	static Php::Extension extension("ebpf", "1.0.0");
+	extension.add<bpf_new>("bpf_new", {Php::ByVal("a", Php::Type::String)});
+	extension.add<bpf_new2>("bpf_new2", {Php::ByVal("a", Php::Type::String)});
+	Php::Class<EbpfExtension> 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_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),
+	});
+
+
+	Php::Class<PerfEvent> ebpf_perf_event_class("PerfEvent");
+	ebpf_perf_event_class.method<&PerfEvent::php_open_perf_buffer>("open_perf_buffer", {
+			Php::ByVal("callback", Php::Type::Callable),
+	});
+
+	extension.add(std::move(ebpf_class));
+	extension.add(std::move(ebpf_perf_event_class));
+	return extension;
+}
+}
+

+ 42 - 0
test.php

@@ -0,0 +1,42 @@
+<?php
+function cb($cpu,$data,$size) {
+    $event = unpack("Qcpu/Qts/Qmagic/A16msg", $data);
+    if ($event === false) {
+        echo "error.\n";
+        return;
+    }
+    printf("[%d] %.6f: %x %s\n", $event['cpu'], $event['ts'] / 1000000, $event['magic'],$event['msg']);
+}
+$prog = <<<EOT
+BPF_PERF_OUTPUT(events);
+BPF_ARRAY(counters, u64, 10);
+int do_sys_clone(void *ctx) {
+  struct {
+    u64 cpu;
+    u64 ts;
+    u64 magic;
+    char msg[16];
+  } data = {bpf_get_smp_processor_id(),bpf_ktime_get_ns(), 0x12345678,"Hello, world!"};
+  int rc;
+  if ((rc = events.perf_submit(ctx, &data, sizeof(data))) < 0)
+    bpf_trace_printk("perf_output failed: %d\\n", rc);
+  int zero = 0;
+  u64 *val = counters.lookup(&zero);
+  if (val) lock_xadd(val, 1);
+  return 0;
+}
+EOT;
+
+$ebpf = new Ebpf($prog);
+$ebpf->attach_kprobe("blk_account_io_done","do_sys_clone");
+$ebpf->perf_event("events")->open_perf_buffer("cb");
+echo("Tracing... Hit Ctrl-C to end.\n");
+
+while (true) {
+    try {
+        $ebpf->perf_buffer_poll();
+        flush();
+    } catch (Exception $e) {
+        exit;
+    }
+}