Skip to content

Commit

Permalink
tools: add support for AOT cli (#257)
Browse files Browse the repository at this point in the history
* fix

* update examples for aot cli

* fix uprobe example

* add run native ELF
  • Loading branch information
yunwei37 authored Mar 24, 2024
1 parent c828a48 commit 903c951
Show file tree
Hide file tree
Showing 17 changed files with 682 additions and 10 deletions.
2 changes: 1 addition & 1 deletion example/minimal/uprobe.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ int do_uprobe_trace(struct pt_regs *ctx)
return 0;
}

char LICENSE[] SEC("license") = "GPL";
char LICENSE[] SEC("license") = "GPL";
2 changes: 1 addition & 1 deletion runtime/include/bpftime_prog.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class bpftime_prog {
{
return insns;
}

const struct ebpf_vm *get_vm() const { return vm; }
private:
int bpftime_prog_set_insn(struct ebpf_inst *insn, size_t insn_cnt);
std::string name;
Expand Down
6 changes: 5 additions & 1 deletion tools/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
add_subdirectory(bpftimetool)
add_subdirectory(cli-cpp)
add_subdirectory(cli)
if(BPFTIME_LLVM_JIT)
message(STATUS "Using llvm-jit")
add_subdirectory(aot)
endif()
23 changes: 23 additions & 0 deletions tools/aot/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
add_executable(
bpftime-aot-cli
main.cpp
)

set_target_properties(bpftime-aot-cli PROPERTIES OUTPUT_NAME "bpftime-aot")

target_include_directories(bpftime-aot-cli PRIVATE
${SPDLOG_INCLUDE}
${argparse_INCLUDE}
${CMAKE_CURRENT_SOURCE_DIR}/../../vm/llvm-jit/src
${CMAKE_CURRENT_SOURCE_DIR}/../vm/llvm-jit/include
../../runtime/include/
../../runtime/src/
${LIBBPF_INCLUDE_DIRS})
target_link_libraries(bpftime-aot-cli PRIVATE spdlog::spdlog argparse vm-bpf runtime ${LIBBPF_LIBRARIES} elf z)
set_property(TARGET bpftime-aot-cli PROPERTY CXX_STANDARD 20)

target_compile_definitions(bpftime-aot-cli PRIVATE _GNU_SOURCE)

add_dependencies(bpftime-aot-cli spdlog::spdlog argparse vm-bpf libbpf)

install(TARGETS bpftime-aot-cli CONFIGURATIONS Release Debug RelWithDebInfo DESTINATION ~/.bpftime)
233 changes: 233 additions & 0 deletions tools/aot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
# bpftime-aot cli

An cli for help to compile eBPF to native ELF.

It can be used to compile eBPF insns to native insns with helpers, maps define, or load native ELF to run.

## Usage

```console
# bpftime-aot help
Usage: /home/yunwei/ebpf-xdp-dpdk/build-bpftime/bpftime/tools/aot/bpftime-aot [--help] [--version] {build,compile,run}

Optional arguments:
-h, --help shows help message and exits
-v, --version prints version information and exits

Subcommands:
build Build native ELF(s) from eBPF ELF. Each program in the eBPF ELF will be built into a single native ELF
compile Compile the eBPF program loaded in shared memory
run Run an native eBPF program
```

## Build ELF from shared mnemory and use it with helpers and maps

load the eBPF programs and maps to shared memory:

```sh
LD_PRELOAD=build/runtime/syscall-server/libbpftime-syscall-server.so example/malloc/malloc
```

The eBPF code here is:

```c
#define BPF_NO_GLOBAL_DATA
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, u32);
__type(value, u64);
} libc_malloc_calls_total SEC(".maps");

static int increment_map(void *map, void *key, u64 increment)
{
u64 zero = 0, *count = bpf_map_lookup_elem(map, key);
if (!count) {
bpf_map_update_elem(map, key, &zero, BPF_NOEXIST);
count = bpf_map_lookup_elem(map, key);
if (!count) {
return 0;
}
}
u64 res = *count + increment;
bpf_map_update_elem(map, key, &res, BPF_EXIST);

return *count;
}

SEC("uprobe/libc.so.6:malloc")
int do_count(struct pt_regs *ctx)
{
u32 pid = bpf_get_current_pid_tgid() >> 32;

bpf_printk("malloc called from pid %d\n", pid);

increment_map(&libc_malloc_calls_total, &pid, 1);

return 0;
}

char LICENSE[] SEC("license") = "GPL";
```
then build the native ELF from shared memory:
```sh
bpftime-aot compile
```

You will get a native ELF file named `do_count.o`.

You can link it with your program and execute it:

```sh
cd example
clang -O2 main.c do_count.o -o malloc
```

The drive program is like:

```c
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>

int bpf_main(void* ctx, uint64_t size);

// bpf_printk
uint64_t _bpf_helper_ext_0006(uint64_t fmt, uint64_t fmt_size, ...)
{
const char *fmt_str = (const char *)fmt;
va_list args;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wvarargs"
va_start(args, fmt_str);
long ret = vprintf(fmt_str, args);
#pragma GCC diagnostic pop
va_end(args);
return 0;
}

// bpf_get_current_pid_tgid
uint64_t _bpf_helper_ext_0014(void)
{
static int tgid = -1;
static int tid = -1;
if (tid == -1)
tid = gettid();
if (tgid == -1)
tgid = getpid();
return ((uint64_t)tgid << 32) | tid;
}

// here we use an var to mock the map.
uint64_t counter_map = 0;

// bpf_map_lookup_elem
void * _bpf_helper_ext_0001(void *map, const void *key)
{
printf("bpf_map_lookup_elem\n");
return &counter_map;
}

// bpf_map_update_elem
long _bpf_helper_ext_0002(void *map, const void *key, const void *value, uint64_t flags)
{
printf("bpf_map_update_elem\n");
if (value == NULL) {
printf("value is NULL\n");
return -1;
}
uint64_t* value_ptr = (uint64_t*)value_ptr;
counter_map = *value_ptr;
printf("counter_map: %lu\n", counter_map);
return 0;
}

uint64_t __lddw_helper_map_by_fd(uint32_t id) {
printf("map_by_fd\n");
return 0;
}

int main() {
printf("Hello, World!\n");
bpf_main(NULL, 0);
return 0;
}
```
Note by loading eBPF programs with libbpf and LD_PRELOAD, maps, global variables, and helpers are already relocated in shared memory, so you can use them directly in your program. For example, the input of `__lddw_helper_map_by_fd` function would be the actual map id in shared memory.
You can refer to `example/malloc.json` for details about how the maps are relocated.
## Compile from eBPF bytecode ELF
You can also compile the eBPF bytecode ELF to native ELF:
```sh
bpftime-aot build bpftime/example/minimal/.output/uprobe.bpf.o -e
```

In this way, the relocation of maps, global variables, and helpers will not be done. The helpers is still works.

## run native ELF

Given a eBPF code:

```c
#define BPF_NO_GLOBAL_DATA
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

SEC("uprobe/./victim:target_func")
int do_uprobe_trace(struct pt_regs *ctx)
{
bpf_printk("target_func called.\n");
return 0;
}

char LICENSE[] SEC("license") = "GPL";
```
The native C code after relocation is like:
```c
int _bpf_helper_ext_0006(char* arg0);
int bpf_main(void *ctx)
{
_bpf_helper_ext_0006("target_func called.\n");
return 0;
}
```

Compile it with `clang -O3 -c -o do_uprobe_trace.o do_uprobe_trace.c`, and you can load it with AOT runtime.

You can simply run the native ELF:

```console
# bpftime-aot run do_uprobe_trace.o
[2024-03-24 21:57:53.446] [info] [llvm_jit_context.cpp:81] Initializing llvm
[2024-03-24 21:57:53.446] [info] [llvm_jit_context.cpp:204] LLVM-JIT: Loading aot object
target_func called.
[2024-03-24 21:57:53.449] [info] [main.cpp:190] Output: 0
```

## emit llvm ir

```sh
bpftime-aot compile -e
```

or:

```sh
bpftime-aot build -e minimal.bpf.o
```
1 change: 1 addition & 0 deletions tools/aot/example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
malloc
6 changes: 6 additions & 0 deletions tools/aot/example/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
malloc: main.c
clang -O2 -flto main.c do_count.o -o malloc

.PHONY: clean
clean:
rm -f malloc
Binary file added tools/aot/example/do_count.o
Binary file not shown.
Binary file added tools/aot/example/do_uprobe_trace.o
Binary file not shown.
68 changes: 68 additions & 0 deletions tools/aot/example/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>

int bpf_main(void* ctx, uint64_t size);

// bpf_printk
uint64_t _bpf_helper_ext_0006(uint64_t fmt, uint64_t fmt_size, ...)
{
const char *fmt_str = (const char *)fmt;
va_list args;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wvarargs"
va_start(args, fmt_str);
long ret = vprintf(fmt_str, args);
#pragma GCC diagnostic pop
va_end(args);
return 0;
}

// bpf_get_current_pid_tgid
uint64_t _bpf_helper_ext_0014(void)
{
static int tgid = -1;
static int tid = -1;
if (tid == -1)
tid = gettid();
if (tgid == -1)
tgid = getpid();
return ((uint64_t)tgid << 32) | tid;
}

// here we use an var to mock the map.
uint64_t counter_map = 0;

// bpf_map_lookup_elem
void * _bpf_helper_ext_0001(void *map, const void *key)
{
printf("bpf_map_lookup_elem\n");
return &counter_map;
}

// bpf_map_update_elem
long _bpf_helper_ext_0002(void *map, const void *key, const void *value, uint64_t flags)
{
printf("bpf_map_update_elem\n");
if (value == NULL) {
printf("value is NULL\n");
return -1;
}
uint64_t* value_ptr = (uint64_t*)value_ptr;
counter_map = *value_ptr;
printf("counter_map: %lu\n", counter_map);
return 0;
}

uint64_t __lddw_helper_map_by_fd(uint32_t id) {
printf("map_by_fd\n");
return 0;
}

int main() {
printf("Hello, World!\n");
bpf_main(NULL, 0);
return 0;
}
Loading

0 comments on commit 903c951

Please sign in to comment.