|
| 1 | +# bpftime-aot cli |
| 2 | + |
| 3 | +An cli for help to compile eBPF to native ELF. |
| 4 | + |
| 5 | +It can be used to compile eBPF insns to native insns with helpers, maps define, or load native ELF to run. |
| 6 | + |
| 7 | +## Usage |
| 8 | + |
| 9 | +```console |
| 10 | +# bpftime-aot help |
| 11 | +Usage: /home/yunwei/ebpf-xdp-dpdk/build-bpftime/bpftime/tools/aot/bpftime-aot [--help] [--version] {build,compile,run} |
| 12 | + |
| 13 | +Optional arguments: |
| 14 | + -h, --help shows help message and exits |
| 15 | + -v, --version prints version information and exits |
| 16 | + |
| 17 | +Subcommands: |
| 18 | + build Build native ELF(s) from eBPF ELF. Each program in the eBPF ELF will be built into a single native ELF |
| 19 | + compile Compile the eBPF program loaded in shared memory |
| 20 | + run Run an native eBPF program |
| 21 | +``` |
| 22 | + |
| 23 | +## Build ELF from shared mnemory and use it with helpers and maps |
| 24 | + |
| 25 | +load the eBPF programs and maps to shared memory: |
| 26 | + |
| 27 | +```sh |
| 28 | +LD_PRELOAD=build/runtime/syscall-server/libbpftime-syscall-server.so example/malloc/malloc |
| 29 | +``` |
| 30 | + |
| 31 | +The eBPF code here is: |
| 32 | + |
| 33 | +```c |
| 34 | +#define BPF_NO_GLOBAL_DATA |
| 35 | +#include <vmlinux.h> |
| 36 | +#include <bpf/bpf_helpers.h> |
| 37 | +#include <bpf/bpf_tracing.h> |
| 38 | + |
| 39 | +struct { |
| 40 | + __uint(type, BPF_MAP_TYPE_HASH); |
| 41 | + __uint(max_entries, 1024); |
| 42 | + __type(key, u32); |
| 43 | + __type(value, u64); |
| 44 | +} libc_malloc_calls_total SEC(".maps"); |
| 45 | + |
| 46 | +static int increment_map(void *map, void *key, u64 increment) |
| 47 | +{ |
| 48 | + u64 zero = 0, *count = bpf_map_lookup_elem(map, key); |
| 49 | + if (!count) { |
| 50 | + bpf_map_update_elem(map, key, &zero, BPF_NOEXIST); |
| 51 | + count = bpf_map_lookup_elem(map, key); |
| 52 | + if (!count) { |
| 53 | + return 0; |
| 54 | + } |
| 55 | + } |
| 56 | + u64 res = *count + increment; |
| 57 | + bpf_map_update_elem(map, key, &res, BPF_EXIST); |
| 58 | + |
| 59 | + return *count; |
| 60 | +} |
| 61 | + |
| 62 | +SEC("uprobe/libc.so.6:malloc") |
| 63 | +int do_count(struct pt_regs *ctx) |
| 64 | +{ |
| 65 | + u32 pid = bpf_get_current_pid_tgid() >> 32; |
| 66 | + |
| 67 | + bpf_printk("malloc called from pid %d\n", pid); |
| 68 | + |
| 69 | + increment_map(&libc_malloc_calls_total, &pid, 1); |
| 70 | + |
| 71 | + return 0; |
| 72 | +} |
| 73 | + |
| 74 | +char LICENSE[] SEC("license") = "GPL"; |
| 75 | +``` |
| 76 | +
|
| 77 | +then build the native ELF from shared memory: |
| 78 | +
|
| 79 | +```sh |
| 80 | +bpftime-aot compile |
| 81 | +``` |
| 82 | + |
| 83 | +You will get a native ELF file named `do_count.o`. |
| 84 | + |
| 85 | +You can link it with your program and execute it: |
| 86 | + |
| 87 | +```sh |
| 88 | +cd example |
| 89 | +clang -O2 main.c do_count.o -o malloc |
| 90 | +``` |
| 91 | + |
| 92 | +The drive program is like: |
| 93 | + |
| 94 | +```c |
| 95 | +#include <stdio.h> |
| 96 | +#include <stdint.h> |
| 97 | +#include <unistd.h> |
| 98 | +#include <stdlib.h> |
| 99 | + |
| 100 | +int bpf_main(void* ctx, uint64_t size); |
| 101 | + |
| 102 | +// bpf_printk |
| 103 | +uint64_t _bpf_helper_ext_0006(uint64_t fmt, uint64_t fmt_size, ...) |
| 104 | +{ |
| 105 | + const char *fmt_str = (const char *)fmt; |
| 106 | + va_list args; |
| 107 | +#pragma GCC diagnostic push |
| 108 | +#pragma GCC diagnostic ignored "-Wformat-nonliteral" |
| 109 | +#pragma GCC diagnostic ignored "-Wvarargs" |
| 110 | + va_start(args, fmt_str); |
| 111 | + long ret = vprintf(fmt_str, args); |
| 112 | +#pragma GCC diagnostic pop |
| 113 | + va_end(args); |
| 114 | + return 0; |
| 115 | +} |
| 116 | + |
| 117 | +// bpf_get_current_pid_tgid |
| 118 | +uint64_t _bpf_helper_ext_0014(void) |
| 119 | +{ |
| 120 | + static int tgid = -1; |
| 121 | + static int tid = -1; |
| 122 | + if (tid == -1) |
| 123 | + tid = gettid(); |
| 124 | + if (tgid == -1) |
| 125 | + tgid = getpid(); |
| 126 | + return ((uint64_t)tgid << 32) | tid; |
| 127 | +} |
| 128 | + |
| 129 | +// here we use an var to mock the map. |
| 130 | +uint64_t counter_map = 0; |
| 131 | + |
| 132 | +// bpf_map_lookup_elem |
| 133 | +void * _bpf_helper_ext_0001(void *map, const void *key) |
| 134 | +{ |
| 135 | + printf("bpf_map_lookup_elem\n"); |
| 136 | + return &counter_map; |
| 137 | +} |
| 138 | + |
| 139 | +// bpf_map_update_elem |
| 140 | +long _bpf_helper_ext_0002(void *map, const void *key, const void *value, uint64_t flags) |
| 141 | +{ |
| 142 | + printf("bpf_map_update_elem\n"); |
| 143 | + if (value == NULL) { |
| 144 | + printf("value is NULL\n"); |
| 145 | + return -1; |
| 146 | + } |
| 147 | + uint64_t* value_ptr = (uint64_t*)value_ptr; |
| 148 | + counter_map = *value_ptr; |
| 149 | + printf("counter_map: %lu\n", counter_map); |
| 150 | + return 0; |
| 151 | +} |
| 152 | + |
| 153 | +uint64_t __lddw_helper_map_by_fd(uint32_t id) { |
| 154 | + printf("map_by_fd\n"); |
| 155 | + return 0; |
| 156 | +} |
| 157 | + |
| 158 | +int main() { |
| 159 | + printf("Hello, World!\n"); |
| 160 | + bpf_main(NULL, 0); |
| 161 | + return 0; |
| 162 | +} |
| 163 | +``` |
| 164 | +
|
| 165 | +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. |
| 166 | +
|
| 167 | +You can refer to `example/malloc.json` for details about how the maps are relocated. |
| 168 | +
|
| 169 | +## Compile from eBPF bytecode ELF |
| 170 | +
|
| 171 | +You can also compile the eBPF bytecode ELF to native ELF: |
| 172 | +
|
| 173 | +```sh |
| 174 | +bpftime-aot build bpftime/example/minimal/.output/uprobe.bpf.o -e |
| 175 | +``` |
| 176 | + |
| 177 | +In this way, the relocation of maps, global variables, and helpers will not be done. The helpers is still works. |
| 178 | + |
| 179 | +## run native ELF |
| 180 | + |
| 181 | +Given a eBPF code: |
| 182 | + |
| 183 | +```c |
| 184 | +#define BPF_NO_GLOBAL_DATA |
| 185 | +#include <vmlinux.h> |
| 186 | +#include <bpf/bpf_helpers.h> |
| 187 | +#include <bpf/bpf_tracing.h> |
| 188 | + |
| 189 | +SEC("uprobe/./victim:target_func") |
| 190 | +int do_uprobe_trace(struct pt_regs *ctx) |
| 191 | +{ |
| 192 | + bpf_printk("target_func called.\n"); |
| 193 | + return 0; |
| 194 | +} |
| 195 | + |
| 196 | +char LICENSE[] SEC("license") = "GPL"; |
| 197 | +``` |
| 198 | +
|
| 199 | +The native C code after relocation is like: |
| 200 | +
|
| 201 | +```c |
| 202 | +int _bpf_helper_ext_0006(char* arg0); |
| 203 | +
|
| 204 | +int bpf_main(void *ctx) |
| 205 | +{ |
| 206 | + _bpf_helper_ext_0006("target_func called.\n"); |
| 207 | + return 0; |
| 208 | +} |
| 209 | +``` |
| 210 | + |
| 211 | +Compile it with `clang -O3 -c -o do_uprobe_trace.o do_uprobe_trace.c`, and you can load it with AOT runtime. |
| 212 | + |
| 213 | +You can simply run the native ELF: |
| 214 | + |
| 215 | +```console |
| 216 | +# bpftime-aot run do_uprobe_trace.o |
| 217 | +[2024-03-24 21:57:53.446] [info] [llvm_jit_context.cpp:81] Initializing llvm |
| 218 | +[2024-03-24 21:57:53.446] [info] [llvm_jit_context.cpp:204] LLVM-JIT: Loading aot object |
| 219 | +target_func called. |
| 220 | +[2024-03-24 21:57:53.449] [info] [main.cpp:190] Output: 0 |
| 221 | +``` |
| 222 | + |
| 223 | +## emit llvm ir |
| 224 | + |
| 225 | +```sh |
| 226 | +bpftime-aot compile -e |
| 227 | +``` |
| 228 | + |
| 229 | +or: |
| 230 | + |
| 231 | +```sh |
| 232 | +bpftime-aot build -e minimal.bpf.o |
| 233 | +``` |
0 commit comments