Skip to content

Commit

Permalink
Add 32-bit unconditional jump instruction
Browse files Browse the repository at this point in the history
This instruction is part of the cpu=v4 set of instructions
recently added, and specified in
https://www.ietf.org/archive/id/draft-ietf-bpf-isa-00.html#jump-instructions

Fixes #532

Signed-off-by: Dave Thaler <[email protected]>
  • Loading branch information
dthaler committed Dec 27, 2023
1 parent 38ebf36 commit f269a44
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 11 deletions.
15 changes: 11 additions & 4 deletions src/asm_marshal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ struct MarshalVisitor {
}

public:
std::function<auto(label_t)->int16_t> label_to_offset;
std::function<auto(label_t)->int16_t> label_to_offset16;
std::function<auto(label_t)->int32_t> label_to_offset32;

vector<ebpf_inst> operator()(Undefined const& a) {
assert(false);
Expand Down Expand Up @@ -179,7 +180,7 @@ struct MarshalVisitor {
.opcode = static_cast<uint8_t>(INST_CLS_JMP | (op(b.cond->op) << 4)),
.dst = b.cond->left.v,
.src = 0,
.offset = label_to_offset(b.target),
.offset = label_to_offset16(b.target),
};
visit(overloaded{[&](Reg right) {
res.opcode |= INST_SRC_REG;
Expand All @@ -189,7 +190,11 @@ struct MarshalVisitor {
b.cond->right);
return {res};
} else {
return {ebpf_inst{.opcode = INST_OP_JA, .dst = 0, .src = 0, .offset = label_to_offset(b.target), .imm = 0}};
int32_t imm = label_to_offset32(b.target);
if (imm != 0)
return {ebpf_inst{.opcode = INST_OP_JA32, .imm = imm}};
else
return {ebpf_inst{.opcode = INST_OP_JA16, .offset = label_to_offset16(b.target)}};
}
}

Expand Down Expand Up @@ -252,7 +257,9 @@ struct MarshalVisitor {
}
};

vector<ebpf_inst> marshal(const Instruction& ins, pc_t pc) { return std::visit(MarshalVisitor{label_to_offset(pc)}, ins); }
vector<ebpf_inst> marshal(const Instruction& ins, pc_t pc) {
return std::visit(MarshalVisitor{label_to_offset16(pc), label_to_offset32(pc)}, ins);
}

vector<ebpf_inst> marshal(const vector<Instruction>& insts) {
vector<ebpf_inst> res;
Expand Down
17 changes: 15 additions & 2 deletions src/asm_ostream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,21 @@

#include "asm_syntax.hpp"

inline std::function<int16_t(label_t)> label_to_offset(pc_t pc) {
return [=](const label_t& label) { return label.from - pc - 1; };
// We use a 16-bit offset whenever it fits in 16 bits.
inline std::function<int16_t(label_t)> label_to_offset16(pc_t pc) {
return [=](const label_t& label) {
int64_t offset = label.from - (int64_t)pc - 1;
return (offset >= INT16_MIN && offset <= INT16_MAX) ? (int16_t)offset : 0;
};
}

// We use the JA32 opcode with the offset in 'imm' when the offset
// of an unconditional jump doesn't fit in a int16_t.
inline std::function<int32_t(label_t)> label_to_offset32(pc_t pc) {
return [=](const label_t& label) {
int64_t offset = label.from - (int64_t)pc - 1;
return (offset >= INT16_MIN && offset <= INT16_MAX) ? 0 : (int32_t)offset;
};
}

std::ostream& operator<<(std::ostream& os, const btf_line_info_t& line_info);
Expand Down
3 changes: 2 additions & 1 deletion src/asm_syntax.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,8 @@ using InstructionSeq = std::vector<LabeledInstruction>;
#define DECLARE_EQ1(T, f1) \
inline bool operator==(T const& a, T const& b) { return a.f1 == b.f1; }

using pc_t = uint16_t;
// cpu=v4 supports 32-bit PC offsets so we need a large enough type.
using pc_t = size_t;

// Helpers:

Expand Down
11 changes: 7 additions & 4 deletions src/asm_unmarshal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -381,16 +381,19 @@ struct Unmarshaller {
throw InvalidInstruction(pc, "Bad instruction");
return Exit{};
case 0x0:
if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP)
if ((inst.opcode & INST_CLS_MASK) != INST_CLS_JMP &&
(inst.opcode & INST_CLS_MASK) != INST_CLS_JMP32)
throw InvalidInstruction(pc, "Bad instruction");
default: {
pc_t new_pc = pc + 1 + inst.offset;
int32_t offset = (inst.opcode == INST_OP_JA32) ? inst.imm : inst.offset;
pc_t new_pc = pc + 1 + offset;
if (new_pc >= insts.size())
throw InvalidInstruction(pc, "jump out of bounds");
else if (insts[new_pc].opcode == 0)
throw InvalidInstruction(pc, "jump to middle of lddw");

auto cond = inst.opcode == INST_OP_JA ? std::optional<Condition>{}
auto cond = (inst.opcode == INST_OP_JA16 || inst.opcode == INST_OP_JA32)
? std::optional<Condition>{}
: Condition{
.op = getJmpOp(pc, inst.opcode),
.left = Reg{inst.dst},
Expand All @@ -400,7 +403,7 @@ struct Unmarshaller {
};
return Jmp{
.cond = cond,
.target = label_t{new_pc},
.target = label_t{(int)new_pc},
};
}
}
Expand Down
26 changes: 26 additions & 0 deletions src/test/test_marshal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@
#include "asm_marshal.hpp"
#include "asm_unmarshal.hpp"

// Verify that if we unmarshal an instruction and then re-marshal it,
// we get what we expect.
static void compare_unmarshal_marshal(const ebpf_inst& ins, const ebpf_inst& expected_result) {
program_info info{.platform = &g_ebpf_platform_linux,
.type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")};
const ebpf_inst exit{.opcode = INST_OP_EXIT};
InstructionSeq parsed = std::get<InstructionSeq>(unmarshal(raw_program{"", "", {ins, exit, exit}, info}));
REQUIRE(parsed.size() == 3);
auto [_, single, _2] = parsed.front();
(void)_; // unused
(void)_2; // unused
std::vector<ebpf_inst> marshaled = marshal(single, 0);
REQUIRE(marshaled.size() == 1);
ebpf_inst result = marshaled.back();
REQUIRE(memcmp(&expected_result, &result, sizeof(result)) == 0);
}

// Verify that if we marshal an instruction and then unmarshal it,
// we get the original.
static void compare_marshal_unmarshal(const Instruction& ins, bool double_cmd = false) {
program_info info{.platform = &g_ebpf_platform_linux,
.type = g_ebpf_platform_linux.get_program_type("unspec", "unspec")};
Expand Down Expand Up @@ -81,6 +100,13 @@ TEST_CASE("disasm_marshal", "[disasm][marshal]") {
// Condition::Op::NSET, does not exist in ebpf
Condition::Op::NE, Condition::Op::SGT, Condition::Op::SGE, Condition::Op::LT, Condition::Op::LE,
Condition::Op::SLT, Condition::Op::SLE};
SECTION("goto offset") {
ebpf_inst jmp_offset{.opcode = INST_OP_JA16, .offset = 1};
compare_unmarshal_marshal(jmp_offset, jmp_offset);

// JA32 +1 is equivalent to JA16 +1 since the offset fits in 16 bits.
compare_unmarshal_marshal(ebpf_inst{.opcode = INST_OP_JA32, .imm = 1}, jmp_offset);
}
SECTION("Reg right") {
for (auto op : ops) {
Condition cond{.op = op, .left = Reg{1}, .right = Reg{2}};
Expand Down

0 comments on commit f269a44

Please sign in to comment.