Skip to content

Commit

Permalink
vm: implement CALL instruction
Browse files Browse the repository at this point in the history
Before loading code into the VM, the application should use the ubpf_register
API to associate C functions to integer indices. The eBPF program can then use
these indicies as the immediate in the CALL to pick which function to call.

The 'name' argument is currently unused, but will be used later by the ELF
loader.
  • Loading branch information
rlane committed Sep 17, 2015
1 parent c40586a commit e278e90
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 7 deletions.
14 changes: 14 additions & 0 deletions tests/call-memfrob.data
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- asm
mov r6, r1
add r1, 2
mov r2, 4
call 1
ldxdw r0, [r6]
be64 r0
exit
-- mem
01 02 03 04 05 06 07 08
-- result
0x102292e2f2c0708
-- no jit
CALL not yet implemented
12 changes: 12 additions & 0 deletions tests/call.data
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- asm
mov r1, 1
mov r2, 2
mov r3, 3
mov r4, 4
mov r5, 5
call 0
exit
-- result
0x0102030405
-- no jit
CALL not yet implemented
10 changes: 10 additions & 0 deletions tests/err-call-bad-imm.data
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- asm
mov r1, 1
mov r2, 2
mov r3, 3
mov r4, 4
mov r5, 5
call 64
exit
-- error
Failed to load code: invalid call immediate at PC 5
10 changes: 10 additions & 0 deletions tests/err-call-unreg.data
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- asm
mov r1, 1
mov r2, 2
mov r3, 3
mov r4, 4
mov r5, 5
call 63
exit
-- error
Failed to load code: call to nonexistent function 63 at PC 5
16 changes: 15 additions & 1 deletion vm/inc/ubpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,24 @@ typedef uint64_t (*ubpf_jit_fn)(void *mem, size_t mem_len);
struct ubpf_vm *ubpf_create(void);
void ubpf_destroy(struct ubpf_vm *vm);

/*
* Register an external function
*
* The immediate field of a CALL instruction is an index into an array of
* functions registered by the user. This API associates a function with
* an index.
*
* 'name' should be a string with a lifetime longer than the VM.
*
* Returns 0 on success, -1 on error.
*/
int ubpf_register(struct ubpf_vm *vm, unsigned int idx, const char *name, void *fn);

/*
* Load code into a VM
*
* This must be done before calling ubpf_exec or ubpf_compile.
* This must be done before calling ubpf_exec or ubpf_compile and after
* registering all functions.
*
* 'code' should point to eBPF bytecodes and 'code_len' should be the size in
* bytes of that buffer.
Expand Down
21 changes: 21 additions & 0 deletions vm/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

#define _GNU_SOURCE
#include <inttypes.h>
#include <stdlib.h>
#include <stdbool.h>
Expand All @@ -26,6 +27,7 @@

void ubpf_set_register_offset(int x);
static void *readfile(const char *path, size_t maxlen, size_t *len);
static void register_functions(struct ubpf_vm *vm);

static void usage(const char *name)
{
Expand Down Expand Up @@ -97,6 +99,8 @@ int main(int argc, char **argv)
return 1;
}

register_functions(vm);

char *errmsg;
if (ubpf_load(vm, code, code_len, &errmsg) < 0) {
fprintf(stderr, "Failed to load code: %s\n", errmsg);
Expand Down Expand Up @@ -167,3 +171,20 @@ static void *readfile(const char *path, size_t maxlen, size_t *len)
}
return data;
}

static uint64_t
gather_bytes(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e)
{
return ((uint64_t)a << 32) |
((uint32_t)b << 24) |
((uint32_t)c << 16) |
((uint16_t)d << 8) |
e;
}

static void
register_functions(struct ubpf_vm *vm)
{
ubpf_register(vm, 0, "gather_bytes", gather_bytes);
ubpf_register(vm, 1, "memfrob", memfrob);
}
3 changes: 3 additions & 0 deletions vm/ubpf_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@
#include "ebpf.h"

struct ebpf_inst;
typedef uint64_t (*ext_func)(uint64_t arg0, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t arg4);

struct ubpf_vm {
struct ebpf_inst *insts;
uint16_t num_insts;
ubpf_jit_fn jitted;
size_t jitted_size;
ext_func *ext_funcs;
const char **ext_func_names;
};

char *ubpf_error(const char *fmt, ...);
Expand Down
56 changes: 50 additions & 6 deletions vm/ubpf_vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,32 @@

#define MAX_INSTS 65536
#define STACK_SIZE 128
#define MAX_EXT_FUNCS 64

static bool validate(const struct ebpf_inst *insts, uint32_t num_insts, char **errmsg);
static bool validate(const struct ubpf_vm *vm, const struct ebpf_inst *insts, uint32_t num_insts, char **errmsg);
static bool bounds_check(void *addr, int size, const char *type, uint16_t cur_pc, void *mem, size_t mem_len, void *stack);

struct ubpf_vm *
ubpf_create(void)
{
return calloc(1, sizeof(struct ubpf_vm));
struct ubpf_vm *vm = calloc(1, sizeof(*vm));
if (vm == NULL) {
return NULL;
}

vm->ext_funcs = calloc(MAX_EXT_FUNCS, sizeof(*vm->ext_funcs));
if (vm->ext_funcs == NULL) {
ubpf_destroy(vm);
return NULL;
}

vm->ext_func_names = calloc(MAX_EXT_FUNCS, sizeof(*vm->ext_func_names));
if (vm->ext_func_names == NULL) {
ubpf_destroy(vm);
return NULL;
}

return vm;
}

void
Expand All @@ -43,9 +61,23 @@ ubpf_destroy(struct ubpf_vm *vm)
munmap(vm->jitted, vm->jitted_size);
}
free(vm->insts);
free(vm->ext_funcs);
free(vm->ext_func_names);
free(vm);
}

int
ubpf_register(struct ubpf_vm *vm, unsigned int idx, const char *name, void *fn)
{
if (idx >= MAX_EXT_FUNCS) {
return -1;
}

vm->ext_funcs[idx] = (ext_func)fn;
vm->ext_func_names[idx] = name;
return 0;
}

int
ubpf_load(struct ubpf_vm *vm, const void *code, uint32_t code_len, char **errmsg)
{
Expand All @@ -61,7 +93,7 @@ ubpf_load(struct ubpf_vm *vm, const void *code, uint32_t code_len, char **errmsg
return -1;
}

if (!validate(code, code_len/8, errmsg)) {
if (!validate(vm, code, code_len/8, errmsg)) {
return -1;
}

Expand Down Expand Up @@ -464,14 +496,15 @@ ubpf_exec(const struct ubpf_vm *vm, void *mem, size_t mem_len)
break;
case EBPF_OP_EXIT:
return reg[0];

/* TODO CALL opcode */
case EBPF_OP_CALL:
reg[0] = vm->ext_funcs[inst.imm](reg[1], reg[2], reg[3], reg[4], reg[5]);
break;
}
}
}

static bool
validate(const struct ebpf_inst *insts, uint32_t num_insts, char **errmsg)
validate(const struct ubpf_vm *vm, const struct ebpf_inst *insts, uint32_t num_insts, char **errmsg)
{
if (num_insts >= MAX_INSTS) {
*errmsg = ubpf_error("too many instructions (max %u)", MAX_INSTS);
Expand Down Expand Up @@ -601,6 +634,17 @@ validate(const struct ebpf_inst *insts, uint32_t num_insts, char **errmsg)
}
break;

case EBPF_OP_CALL:
if (inst.imm < 0 || inst.imm >= MAX_EXT_FUNCS) {
*errmsg = ubpf_error("invalid call immediate at PC %d", i);
return false;
}
if (!vm->ext_funcs[inst.imm]) {
*errmsg = ubpf_error("call to nonexistent function %u at PC %d", inst.imm, i);
return false;
}
break;

case EBPF_OP_EXIT:
break;

Expand Down

0 comments on commit e278e90

Please sign in to comment.