Permalink
Browse files

Updated load-bpf.c so that it actually loads eBPF bytecode.

This change introduces `bpf_trace_printk.py`, which I created as a
utility to leverage the Python BPF library to compile a C program
and dump the eBPF bytecode. I was then able to take this bytecode and
inline it into the `load-bpf.c` source file so I could create the
`bpf_insn[]` programmatically.

Although the compiled version of `load-bpf.c` runs, it does not really
do anything yet because it does not attach the kprobe.
  • Loading branch information...
bolinfest committed Sep 13, 2018
1 parent 1a93354 commit 914b24a8c81189d8e278e2e1ce1df53c583f1a5d
Showing with 228 additions and 27 deletions.
  1. +2 −1 .gitignore
  2. +61 −0 c/bpf_trace_printk.py
  3. +4 −0 c/build.sh
  4. +161 −26 c/load-bpf.c
View
@@ -1,3 +1,4 @@
/Cargo.lock
/target
/c/load-bpf
/go/go
/target
View
@@ -0,0 +1,61 @@
from bcc import ArgString, BPF
from binascii import hexlify
import struct
# define BPF program
bpf_text = """
#include <uapi/linux/ptrace.h>
#include <uapi/linux/limits.h>
#include <linux/sched.h>
int trace_entry(struct pt_regs *ctx, int dfd, const char __user *filename)
{
bpf_trace_printk("hello from rust\\n", 17);
return 0;
};
"""
b = BPF(text=bpf_text)
bytecode = b.dump_func("trace_entry")
def print_list_of_instructions():
for index, b_offset in enumerate(xrange(0, len(bytecode), 8)):
instruction = bytecode[b_offset : b_offset + 8]
print("%2d %s" % (index, hexlify(instruction)))
bpf_insn_template = """\
((struct bpf_insn) {
.code = 0x%s,
.dst_reg = BPF_REG_%d,
.src_reg = BPF_REG_%d,
.off = %d,
.imm = %d,
}),"""
def print_bpf_insn_struct():
"""Prints the eBPF bytecode as the equivalent bpf_insn[] in C."""
print("struct bpf_insn prog[] = {")
for b_offset in xrange(0, len(bytecode), 8):
instruction = bytecode[b_offset : b_offset + 8]
# u8
opcode = instruction[0]
src_and_dst = struct.unpack("B", instruction[1])[0]
# The low-order nibble is dst.
dst_reg = src_and_dst & 0x0F
# The high-order nibble is src.
src_reg = (src_and_dst & 0xF0) >> 4
# s16
offset = struct.unpack("h", instruction[2:4])[0]
# s32
imm = struct.unpack("i", instruction[4:8])[0]
print(bpf_insn_template % (hexlify(opcode), dst_reg, src_reg, offset, imm))
print("};")
# print_list_of_instructions()
print_bpf_insn_struct()
View
@@ -0,0 +1,4 @@
#!/bin/sh
# Note the generated load-bpf executable must be
# run with sudo.
clang -g -o load-bpf load-bpf.c
View
@@ -1,6 +1,8 @@
#include <linux/bpf.h>
#include <sys/syscall.h>
#include <linux/version.h>
#include <stdio.h>
#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>
#define LOG_BUF_SIZE 65536
@@ -11,43 +13,176 @@ static inline __u64 ptr_to_u64(const void *ptr) {
return (__u64)(unsigned long)ptr;
}
/**
* Taken from the man page for bpf(2), though two critical lines
* of code that are missing from that man page are:
* (1) The bpf_attr must be zeroed-out before it is used.
* Failing to do so will likely result in an EINVAL when
* doing the BPF_PROG_LOAD.
*
* memset(&attr, 0, sizeof(attr))
*
* (2) kern_version must be defined if the program type is
* BPF_PROG_TYPE_KPROBE. Note that LINUX_VERSION_CODE is defined
* in <linux/version.h>.
*
* attr.kern_version = LINUX_VERSION_CODE;
*/
int bpf_prog_load(enum bpf_prog_type type, const struct bpf_insn *insns,
int insn_cnt, const char *license) {
union bpf_attr attr = {
.prog_type = type,
.insns = ptr_to_u64(insns),
.insn_cnt = insn_cnt,
.license = ptr_to_u64(license),
.log_buf = ptr_to_u64(bpf_log_buf),
.log_size = LOG_BUF_SIZE,
.log_level = 1,
};
union bpf_attr attr;
memset(&attr, 0, sizeof(attr));
attr.prog_type = type;
attr.insns = ptr_to_u64(insns);
attr.insn_cnt = insn_cnt;
attr.license = ptr_to_u64(license);
attr.log_buf = ptr_to_u64(bpf_log_buf);
attr.log_size = LOG_BUF_SIZE;
attr.log_level = 1;
// As noted in bpf(2), kern_version is checked when prog_type=kprobe.
attr.kern_version = LINUX_VERSION_CODE;
// I would like to know why there is no function named bpf()
// exported from linux/bpf.h since bpf(2) suggests I should be able
// to call this directly:
// return bpf(BPF_PROG_LOAD, &attr, sizeof(attr));
// If this returns a non-zero number, printing the contents of
// bpf_log_buf may help. libbpf.c has a bpf_print_hints() function that
// can help with this.
return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
}
int main(int argc, char **argv) {
printf("argc: %d\n", argc);
if (argc < 2) {
fprintf(stderr, "Error: no file specified.\n");
// This array was generated from bpf_trace_printk.py.
struct bpf_insn prog[] = {
((struct bpf_insn){
.code = 0x18,
.dst_reg = BPF_REG_1,
.src_reg = BPF_REG_0,
.off = 0,
.imm = 1914727791,
}),
((struct bpf_insn){
.code = 0x00,
.dst_reg = BPF_REG_0,
.src_reg = BPF_REG_0,
.off = 0,
.imm = 175403893,
}),
((struct bpf_insn){
.code = 0x7b,
.dst_reg = BPF_REG_10,
.src_reg = BPF_REG_1,
.off = -24,
.imm = 0,
}),
((struct bpf_insn){
.code = 0x18,
.dst_reg = BPF_REG_1,
.src_reg = BPF_REG_0,
.off = 0,
.imm = 1819043176,
}),
((struct bpf_insn){
.code = 0x00,
.dst_reg = BPF_REG_0,
.src_reg = BPF_REG_0,
.off = 0,
.imm = 1919295599,
}),
((struct bpf_insn){
.code = 0x7b,
.dst_reg = BPF_REG_10,
.src_reg = BPF_REG_1,
.off = -32,
.imm = 0,
}),
((struct bpf_insn){
.code = 0xb7,
.dst_reg = BPF_REG_1,
.src_reg = BPF_REG_0,
.off = 0,
.imm = 0,
}),
((struct bpf_insn){
.code = 0x73,
.dst_reg = BPF_REG_10,
.src_reg = BPF_REG_1,
.off = -16,
.imm = 0,
}),
((struct bpf_insn){
.code = 0xbf,
.dst_reg = BPF_REG_1,
.src_reg = BPF_REG_10,
.off = 0,
.imm = 0,
}),
((struct bpf_insn){
.code = 0x07,
.dst_reg = BPF_REG_1,
.src_reg = BPF_REG_0,
.off = 0,
.imm = -32,
}),
((struct bpf_insn){
.code = 0xb7,
.dst_reg = BPF_REG_2,
.src_reg = BPF_REG_0,
.off = 0,
.imm = 17,
}),
((struct bpf_insn){
.code = 0xb7,
.dst_reg = BPF_REG_3,
.src_reg = BPF_REG_0,
.off = 0,
.imm = 17,
}),
((struct bpf_insn){
.code = 0x85,
.dst_reg = BPF_REG_0,
.src_reg = BPF_REG_0,
.off = 0,
.imm = 6,
}),
((struct bpf_insn){
.code = 0xb7,
.dst_reg = BPF_REG_0,
.src_reg = BPF_REG_0,
.off = 0,
.imm = 0,
}),
((struct bpf_insn){
.code = 0x95,
.dst_reg = BPF_REG_0,
.src_reg = BPF_REG_0,
.off = 0,
.imm = 0,
}),
};
int insn_cnt = sizeof(prog) / sizeof(struct bpf_insn);
int fd = bpf_prog_load(BPF_PROG_TYPE_KPROBE, prog, insn_cnt, "GPL");
if (fd == -1) {
perror("Error calling bpf_prog_load()");
return 1;
}
// TODO: How to read the BPF bytecode out of the file specified by argv[2]?
// Looking through the code at
// https://github.com/iovisor/gobpf/blob/master/elf/elf.go, this appears to
// be a bit of work, so for now, use the demo in go/main.go instead.
struct bpf_insn prog[] = {};
int fd = bpf_prog_load(BPF_PROG_TYPE_KPROBE, prog, sizeof(prog), "GPL");
fprintf(stderr, "Result of bpf_prog_load(): %d\n", fd);
// TODO(mbolin): The program is loaded, but it seems that we need to
// attach a kprobe? bpf_attach_kprobe() is defined in libbpf for this
// purpose.
// Sleep for awhile before closing so the user can tail
fprintf(stderr, "Run "
"`sudo cat /sys/kernel/debug/tracing/trace_pipe`"
" in another terminal to verify bpf_trace_printk()"
" is working as expected.\n");
// Give the user 10s to look at /sys/kernel/debug/tracing/trace_pipe.
// TODO: wait for interrupt instead, which is what go/main.go does.
sleep(10);
// TODO: sleep for awhile before closing so the user can tail
// /sys/kernel/debug/tracing/trace_pipe for events?
close(fd);
return 0;

0 comments on commit 914b24a

Please sign in to comment.