Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azm: Implement JMP rel32 #785

Merged
merged 3 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions azm/amd64/assembler.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
#ifndef AZM_AMD64_ASSEMBLER_H_
#define AZM_AMD64_ASSEMBLER_H_

#include <cassert>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <optional>
#include <utility>
#include <variant>
#include <vector>

namespace azm::amd64 {
Expand Down Expand Up @@ -38,11 +41,46 @@ constexpr std::optional<std::uint8_t> register_index(Reg32 reg) {
return std::nullopt;
}

struct Label {
struct Linked {
std::size_t offset{};
};
struct Unlinked {
std::vector<std::size_t> patch_offsets{};
};

static Label linked(std::size_t jmp_target_offset) { return {Linked{jmp_target_offset}}; }
static Label unlinked() { return {Unlinked{}}; }

std::variant<Linked, Unlinked> v;
};

// https://www.felixcloutier.com/x86/
class Assembler {
public:
[[nodiscard]] std::vector<std::uint8_t> take_assembled() { return std::exchange(assembled_, {}); }

Label label() const { return Label::linked(assembled_.size()); }
Label unlinked_label() const { return Label::unlinked(); }

void link(Label &label) {
assert(std::holds_alternative<Label::Unlinked>(label.v));
static constexpr int kInstructionSize = 4;
std::size_t const jmp_target_offset = assembled_.size();

auto const &unlinked = std::get<Label::Unlinked>(label.v);
for (std::size_t patch_offset : unlinked.patch_offsets) {
auto const rel32 = static_cast<std::uint32_t>(jmp_target_offset - patch_offset - kInstructionSize);
assembled_[patch_offset + 0] = rel32 & 0xff;
assembled_[patch_offset + 1] = (rel32 >> 8) & 0xff;
assembled_[patch_offset + 2] = (rel32 >> 16) & 0xff;
assembled_[patch_offset + 3] = (rel32 >> 24) & 0xff;
}

label = Label::linked(jmp_target_offset);
}

// Instructions
void add(Reg32 dst, Imm32 imm32) {
if (dst != Reg32::Eax) {
std::cerr << "add: Unhandled dst " << static_cast<int>(dst) << '\n';
Expand All @@ -54,6 +92,22 @@ class Assembler {
emit(imm32);
}

void jmp(Label &label) {
// JMP rel32
if (std::holds_alternative<Label::Linked>(label.v)) {
auto const &linked = std::get<Label::Linked>(label.v);
static constexpr int kInstructionSize = 4;
emit(0xe9);
emit(Imm32{static_cast<std::uint32_t>(linked.offset - assembled_.size() - kInstructionSize)});
return;
}

auto &unlinked = std::get<Label::Unlinked>(label.v);
emit(0xe9);
unlinked.patch_offsets.push_back(assembled_.size());
emit(Imm32{0xdeadbeef});
}

void mov(Reg32 dst, Imm32 imm32) {
emit(0xb8 + register_index(dst).value());
emit(imm32);
Expand Down
64 changes: 64 additions & 0 deletions azm/amd64/assembler_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,70 @@ int main() {
a.expect_eq(unsupported_add_code, assembler.take_assembled());
});

s.add_test("JMP, backwards", [](etest::IActions &a) {
Assembler assembler;

auto slot1 = assembler.label();
assembler.jmp(slot1);
assembler.ud2();
assembler.jmp(slot1);
auto slot2 = assembler.label();
assembler.jmp(slot2);

a.expect_eq(assembler.take_assembled(),
CodeVec{
0xe9, // jmp rel32
0xfb, // -5
0xff,
0xff,
0xff,
0x0f, // ud2
0x0b,
0xe9, // jmp rel32
0xf4, // -12
0xff,
0xff,
0xff,
0xe9, // jmp rel32
0xfb, // -5
0xff,
0xff,
0xff,
});
});

s.add_test("JMP, forwards", [](etest::IActions &a) {
Assembler assembler;

auto slot1 = assembler.unlinked_label();
assembler.jmp(slot1);
assembler.ud2();
assembler.jmp(slot1);
assembler.link(slot1);
assembler.jmp(slot1);

a.expect_eq(assembler.take_assembled(),
CodeVec{
0xe9, // jmp rel32
0x07, // 7
0x00,
0x00,
0x00,
0x0f, // ud2
0x0b,
0xe9, // jmp rel32
0x00, // 0
0x00,
0x00,
0x00,
0xe9, // jmp rel32
0xfb, // -5
0xff,
0xff,
0xff,
});
});

s.add_test("MOV r32, imm32", [](etest::IActions &a) {
Assembler assembler;

Expand Down
11 changes: 10 additions & 1 deletion azm/azm_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,22 @@
int main() {
using namespace azm::amd64;
Assembler assembler;
auto forward = assembler.unlinked_label();
assembler.jmp(forward);
assembler.mov(Reg32::Eax, Imm32{3});
assembler.add(Reg32::Eax, Imm32{39});
assembler.mov(Reg32::Ecx, Imm32{0x4321});

auto end = assembler.label();
assembler.ret();

assembler.link(forward);
assembler.mov(Reg32::Edx, Imm32{0x12345678});
assembler.mov(Reg32::Ebx, Imm32{0x1234});
assembler.ret();
assembler.jmp(end);

assembler.ud2();

auto code = assembler.take_assembled();
// Print the machine code in a format usable for something like
// `objdump -D -b binary -mi386:x86-64 -Mintel <file>`.
Expand Down