Skip to content

Commit

Permalink
docs: improve README
Browse files Browse the repository at this point in the history
  • Loading branch information
yunwei37 committed Sep 30, 2023
1 parent c139021 commit ce9f4de
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 141 deletions.
84 changes: 42 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,31 @@
# bpftime: Userspace eBPF runtime for fast Uprobe & Syscall Tracing
# bpftime: Userspace eBPF runtime for fast Uprobe & Syscall hook

Introducing `bpftime`, a full-featured, high-performance eBPF runtime designed to operate in userspace. It's engineered to offer fast Uprobe and Syscall tracing capabilities. Userspace uprobe can be **10x faster than kernel uprobe!**

This makes `bpftime` an ideal choice for use in embedded systems, IoT, edge computing, smart contracts, and cloud-native solutions. It's also compatible with existing eBPF toolchains like clang and libbpf, without requiring any modifications.
`bpftime`, a full-featured, high-performance eBPF runtime designed to operate in userspace. It's engineered to offer fast Uprobe and Syscall hook capabilities. Userspace uprobe can be **10x faster than kernel uprobe!**

> ⚠️ **Note**: `bpftime` is actively under development. The API or design might change in upcoming releases, and it's not yet recommended for production use. See our [roadmap](#roadmap) for details.
## Key Features

- **Unprivileged Userspace eBPF**: Run eBPF programs in userspace, attaching them to Uprobes and Syscall tracepoints just as you would in the kernel.
- **Performance**: Experience up to a 10x speedup in Uprobe overhead compared to kernel uprobe and uretprobe, enhancing your tracing efficiency.
- **Uprobe and Syscall hooks based on binary rewriting**: Run eBPF programs in userspace, attaching them to Uprobes and Syscall tracepoints: **No mannual instrumentation or restart required!**. It can `trace`, `replace` or `patch` the execution of a function, `hook`, `filter` or `redirect` all syscalls of a process safely, and efficiently with an eBPF userspace runtime.
- **Performance**: Experience up to a 10x speedup in Uprobe overhead compared to kernel uprobe and uretprobe.
- **Interprocess eBPF Maps**: Implement userspace eBPF maps in shared userspace memory for summary aggregation or control plane communication.
- **High Compatibility**: Utilize existing eBPF toolchains like clang and libbpf to develop userspace eBPF without any modifications. Fully compatible with kernel eBPF implementations, supporting CO-RE via BTF, and offering userspace host function access.
- **Advanced Tooling**: Benefit from a cross-platform eBPF interpreter and a high-speed JIT compiler powered by LLVM. It also includes a handcrafted x86 JIT in C for limited resources.
- **No instrumentation**: Can inject eBPF runtime into any running process without the need for a restart or manual recompilation. It can run not only in Linux but also in all Unix systems, Windows, and even IoT devices.
- **Compatibility**: use existing eBPF toolchains like clang and libbpf to develop userspace eBPF without any modifications. Supporting CO-RE via BTF, and offering userspace host function access.
- **JIT Support**: Benefit from a cross-platform eBPF interpreter and a high-speed JIT compiler powered by LLVM. It also includes a handcrafted x86 JIT in C for limited resources.
- **No instrumentation**: Can inject eBPF runtime into any running process without the need for a restart or manual recompilation.

## Components

- [`vm`](vm): The eBPF VM and JIT for eBPF, you can choose from LLVM JIT and a simple JIT/interpreter based on ubpf. It can be built as a standalone library and integrated into other projects.
- [`runtime`](runtime): The userspace runtime for eBPF, including the syscall server and agent, attaching eBPF programs to Uprobes and Syscall tracepoints, and eBPF maps in shared memory.

## Quick Start

With `bpftime`, you can build eBPF applications using familiar tools like clang and libbpf, and execute them in userspace. For instance, the `malloc` eBPF program traces malloc calls using uprobe and aggregates the counts using a hash map.

To get started:

### Build and install cli tool
You can refer to [documents/build-and-test.md](documents/build-and-test.md) for how to build the project.

```console
sudo apt-get install libelf-dev zlib1g-dev # Install dependencies
cd tools/cli-rs && cargo build --release
mkdir ~/.bpftime && cp ./target/release/bpftime ~/.bpftime
export PATH=$PATH:~/.bpftime
```
### Build and install runtime
```console
make install # Install the runtime
```
To get started, you can build and run a libbpf based eBPF program starts with `bpftime` cli:

### Inject the server
```console
make -C example/malloc # Build the eBPF program example
bpftime load ./example/malloc/malloc
Expand All @@ -50,17 +41,28 @@ continue malloc...
malloc called from pid 250215
```

You can run it again with another process and you can see the output:
You can also inject the userspace runtime library into a running process:

```console

```

You can see the output from original program:

```console
$ bpftime load ./example/malloc/malloc
...
12:44:35
pid=247299 malloc calls: 10
pid=247322 malloc calls: 10
```

Run the target program with eBPF
Run the target program and dynamically attach the eBPF program into it:

```console
```

Alternatively, you can also run the program directly in the kernel eBPF:
Alternatively, you can also run our sample eBPF program directly in the kernel eBPF, to see the similar output:

```console
$ sudo example/malloc/malloc
Expand All @@ -73,6 +75,19 @@ $ sudo example/malloc/malloc

## In-Depth

### **Examples & Use Cases**

We can use the bpftime userspace runtime for:

- `tracing userspace functions with uprobe`: Attach uprobe, uretprobe or all syscall tracepoints(currently x86 only) eBPF programs to a process or a group of processes:
- `malloc`: count the malloc calls in libc by pid
- `tracing all syscalls with tracepoints`
- `opensnoop`: trace file open or close syscalls in a process

More examples can be found in [example](example) dir. More examples are coming soon.

> Some examples may not working now, we are fixing it. You can refer to [benchmark](benchmark) dir for more working examples.
### Comparing with Kernel eBPF Runtime

- `bpftime` allows you to use `clang` and `libbpf` to build eBPF programs, and run them directly in this runtime. We have tested it with a libbpf version in [third_party/libbpf](third_party/libbpf).
Expand All @@ -91,21 +106,6 @@ The inline hook implementation is based on frida.

see [documents/how-it-works.md](documents/how-it-works.md) for details.

### **Examples & Use Cases**

We can use the bpftime userspace runtime:

Attach uprobe, uretprobe or all syscall tracepoints(currently x86 only) eBPF programs to a process or a group of processes:

- `malloc`: count the malloc calls in libc by pid
- `opensnoop`: trace file open or close syscalls in a process
- `bash_readline`: trace readline calls in bash [TODO: fix it]
- `sslsniff`: trace SSL/TLS raw text in openssl [TODO: fix it]

Examples can be found in [example](example) dir. More examples are coming soon.

> Some examples may not working now, we are fixing it. You can refer to [benchmark](benchmark) dir for more working examples.
### **Performance Benchmarks**

How is the performance of `userspace uprobe` compared to `kernel uprobes`? Let's take a look at the following benchmark results:
Expand Down Expand Up @@ -150,4 +150,4 @@ Stay tuned for more developments from this promising project! You can find `bpft

This project is licensed under the MIT License.

If you have any questions or suggestions, you can also contact [email protected] or wechat `yunwei2567` for more details!
Contact: <[email protected]>
21 changes: 18 additions & 3 deletions documents/build-and-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,26 @@ Build the complete runtime:
make build
```

For a lightweight build without the runtime (only core library and LLVM JIT):
For a lightweight build without the runtime (only vm library and LLVM JIT):

```bash
make build-core
make build-llvm
make build-vm # build the simple vm with a simple jit
make build-llvm # build the vm with llvm jit
```

### Build and install cli tool

```bash
sudo apt-get install libelf-dev zlib1g-dev # Install dependencies
cd tools/cli-rs && cargo build --release
mkdir ~/.bpftime && cp ./target/release/bpftime ~/.bpftime
export PATH=$PATH:~/.bpftime
```

### Build and install runtime

```bash
make install # Install the runtime
```

## Testing
Expand Down
16 changes: 13 additions & 3 deletions tools/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
# tools
# Tools

## cli
Additional tools for bpftime to improve the user experience.

use for inject dynamic lib of runtime into target process
## cli-rs

Use for inject dynamic lib of runtime into target process, and a wrapper and LD_PRELOAD for `bpftime` libraries.

## Install the cli tools an libraries

```bash
sudo apt-get install libelf-dev zlib1g-dev # Install dependencies
cd tools/cli-rs && cargo build --release
mkdir ~/.bpftime && cp ./target/release/bpftime ~/.bpftime
export PATH=$PATH:~/.bpftime
```
2 changes: 2 additions & 0 deletions tools/cli-rs/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ enum SubCommand {
path: String,
},
}

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
Expand Down Expand Up @@ -102,6 +103,7 @@ fn load_ebpf_object_into_kernel(path: impl AsRef<Path>) -> anyhow::Result<()> {
}
Ok(())
}

fn main() -> anyhow::Result<()> {
let args = Args::parse();
let install_path = PathBuf::from(args.install_location);
Expand Down
132 changes: 39 additions & 93 deletions tools/cli/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,16 @@ static libbpf_object_ptr open_and_attach_libbpf_object(const char *filename)
return obj_ptr;
}

void inject_agent(cli_config &config)
void inject_agent(int target_pid, const char *agent_dynlib_path)
{
// check the args
frida_init();
FridaInjector *injector = frida_injector_new();
GError *error = NULL;
guint id = frida_injector_inject_library_file_sync(
injector, config.target_pid, config.agent_dynlib_path.c_str(),
"bpftime_agent_main", "", NULL, &error);
guint id = frida_injector_inject_library_file_sync(injector, target_pid,
agent_dynlib_path,
"bpftime_agent_main",
"", NULL, &error);

if (error != NULL) {
fprintf(stderr, "%s\n", error->message);
Expand All @@ -99,6 +100,29 @@ void inject_agent(cli_config &config)
frida_deinit();
}

std::string get_lib_path(const char *library_name)
{
struct stat st;
auto so_path =
std::string(DEFAULT_INSTALLATION_LOCATION) + library_name;
if (stat(so_path.c_str(), &st) != 0) {
cerr << "Error: necessary library " << so_path
<< " not found:" << errno << endl;
exit(1);
}
return so_path;
}

std::string get_agent_lib_path()
{
return get_lib_path("/libbpftime-agent.so");
}

std::string get_server_lib_path()
{
return get_lib_path("/libbpftime-syscall-server.so");
}

// Main program
int main(int argc, char *argv[])
{
Expand All @@ -107,21 +131,15 @@ int main(int argc, char *argv[])
<< endl;
return 1;
}

auto cmd = std::string(argv[1]);
// struct stat st;
if (cmd == "load") {
if (argc != 3) {
cerr << "Usage: " << argv[0] << " load <EXECUTABLE>"
<< endl;
return 1;
}
auto so_path = std::string(DEFAULT_INSTALLATION_LOCATION) +
"/libbpftime-syscall-server.so";
// if (stat(so_path.c_str(), &st) != 0) {
// cerr << "Error: necessary library " << so_path
// << " not found:" << errno << endl;
// return 1;
// }
auto so_path = get_server_lib_path();
auto command_to_run =
"LD_PRELOAD=" + so_path + " " + std::string(argv[2]);
return system(command_to_run.c_str());
Expand All @@ -131,94 +149,22 @@ int main(int argc, char *argv[])
<< endl;
return 1;
}
auto so_path = std::string(DEFAULT_INSTALLATION_LOCATION) +
"/libbpftime-agent.so";
// if (stat(so_path.c_str(), &st) != 0) {
// cerr << "Error: necessary library " << so_path
// << " not found:" << errno << endl;
// return 1;
// }
auto so_path = get_agent_lib_path();
auto command_to_run =
"LD_PRELOAD=" + so_path + " " + std::string(argv[2]);
return system(command_to_run.c_str());
} else if (cmd == "attach") {
if (argc != 3) {
cerr << "Usage: " << argv[0] << " attach <pid>" << endl;
return 1;
}
// convert pid to int
int pid = atoi(argv[2]);
auto so_path = get_agent_lib_path();
return inject_agent(pid, so_path.c_str());
} else {
cerr << "Invalid subcommand " << cmd << endl;
return 1;
}
// cli_config config;

// namespace po = boost::program_options;

// po::options_description global("Allowed options");

// global.add_options()("help,h", "Print help information")(
// "command", po::value<std::string>(), "Subcommand to run")(
// "subargs", po::value<std::vector<std::string> >(),
// "Arguments for the command")("version,v", "Print version")(
// "dry-run", po::bool_switch(&config.dry_run), "Dry run");
// // desc.add_options()("help,h", "Print help
// information")("version,v",
// // "Print version")(
// // "dry-run", po::bool_switch(&config.dry_run),
// // "dry run")("benchmark", "run the benchmark userspace function")(
// // "kernel-uprobe", po::bool_switch(&config.kernel_uprobe),
// // "Enable kernel uprobe")(
// // "pid,p", po::value<int>(&config.pid)->default_value(0),
// // "Process ID")("input-file",
// // po::value<std::string>(&config.input_file),
// // "Input file name");

// // po::options_description hidden("Hidden options");
// // hidden.add_options()("bpf-object-path",
// // po::value<std::string>(&config.bpf_object_path),
// // "BPF object file path");

// // po::options_description cmdline_options;
// // cmdline_options.add(desc).add(hidden);

// po::options_description visible_options;
// visible_options.add(global);

// // po::positional_options_description positionalOptions;
// // positionalOptions.add("bpf-object-path", 1);

// po::positional_options_description pos;
// pos.add("command", 1).add("subargs", -1);

// std::cout << "start bpftime cli" << std::endl;

// po::variables_map vm;
// try {
// po::store(po::command_line_parser(argc, argv)
// .options(global)
// .positional(pos)
// .allow_unregistered()
// .run(),
// vm);
// po::notify(vm);
// } catch (const po::required_option &e) {
// std::cerr << "Error: " << e.what() << std::endl;
// return 1;
// }
// // cout<<vm<<endl;
// std::string cmd = vm["command"].as<std::string>();

// if (vm.count("help")) {
// std::cout << visible_options << std::endl;
// return 0;
// }

// if (vm.count("version")) {
// std::cout << "Version: " << version << std::endl;
// return 0;
// }

// if (!vm.count("bpf-object-path")) {
// std::cerr << "Error: The <bpf-object-path> option is required."
// << std::endl;
// return 1;
// }

return EXIT_SUCCESS;
}

0 comments on commit ce9f4de

Please sign in to comment.