From 2179c64001a3005af50d5584feef4735a21d8bad Mon Sep 17 00:00:00 2001 From: Robin Linden Date: Wed, 6 Dec 2023 00:03:55 +0100 Subject: [PATCH 1/3] azm: Implement JMP rel32 --- azm/amd64/assembler.h | 14 ++++++++++++++ azm/amd64/assembler_test.cpp | 32 ++++++++++++++++++++++++++++++++ azm/azm_example.cpp | 9 ++++++++- 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/azm/amd64/assembler.h b/azm/amd64/assembler.h index 67a96de7..7b032098 100644 --- a/azm/amd64/assembler.h +++ b/azm/amd64/assembler.h @@ -38,11 +38,18 @@ constexpr std::optional register_index(Reg32 reg) { return std::nullopt; } +struct Label { + std::size_t offset{}; +}; + // https://www.felixcloutier.com/x86/ class Assembler { public: [[nodiscard]] std::vector take_assembled() { return std::exchange(assembled_, {}); } + Label label() const { return Label{assembled_.size()}; } + + // Instructions void add(Reg32 dst, Imm32 imm32) { if (dst != Reg32::Eax) { std::cerr << "add: Unhandled dst " << static_cast(dst) << '\n'; @@ -54,6 +61,13 @@ class Assembler { emit(imm32); } + void jmp(Label label) { + // JMP rel32 + emit(0xe9); + static constexpr int kInstructionSize = 4; + emit(Imm32{static_cast(label.offset - assembled_.size() - kInstructionSize)}); + } + void mov(Reg32 dst, Imm32 imm32) { emit(0xb8 + register_index(dst).value()); emit(imm32); diff --git a/azm/amd64/assembler_test.cpp b/azm/amd64/assembler_test.cpp index f024292e..6c707e7a 100644 --- a/azm/amd64/assembler_test.cpp +++ b/azm/amd64/assembler_test.cpp @@ -41,6 +41,38 @@ 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("MOV r32, imm32", [](etest::IActions &a) { Assembler assembler; diff --git a/azm/azm_example.cpp b/azm/azm_example.cpp index 5f55218e..ef385425 100644 --- a/azm/azm_example.cpp +++ b/azm/azm_example.cpp @@ -14,10 +14,17 @@ int main() { assembler.mov(Reg32::Eax, Imm32{3}); assembler.add(Reg32::Eax, Imm32{39}); assembler.mov(Reg32::Ecx, Imm32{0x4321}); + + auto end = assembler.label(); + assembler.ret(); + + // TODO(robinlinden): jump here. 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 `. From 97c48f3c89976345cd20bc5431bec7d6d5b7c183 Mon Sep 17 00:00:00 2001 From: Robin Linden Date: Wed, 6 Dec 2023 00:04:58 +0100 Subject: [PATCH 2/3] azm: Support JMP to a later offset as well --- azm/amd64/assembler.h | 28 ++++++++++++++++++++++++++++ azm/amd64/assembler_test.cpp | 32 ++++++++++++++++++++++++++++++++ azm/azm_example.cpp | 4 +++- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/azm/amd64/assembler.h b/azm/amd64/assembler.h index 7b032098..fefe4228 100644 --- a/azm/amd64/assembler.h +++ b/azm/amd64/assembler.h @@ -5,6 +5,7 @@ #ifndef AZM_AMD64_ASSEMBLER_H_ #define AZM_AMD64_ASSEMBLER_H_ +#include #include #include #include @@ -42,12 +43,32 @@ struct Label { std::size_t offset{}; }; +struct UnlinkedLabel { + std::vector patch_offsets{}; +}; + // https://www.felixcloutier.com/x86/ class Assembler { public: [[nodiscard]] std::vector take_assembled() { return std::exchange(assembled_, {}); } Label label() const { return Label{assembled_.size()}; } + UnlinkedLabel unlinked_label() const { return UnlinkedLabel{}; } + + Label link(UnlinkedLabel const &label) { + static constexpr int kInstructionSize = 4; + std::size_t const jmp_target_offset = assembled_.size(); + + for (std::size_t patch_offset : label.patch_offsets) { + auto const rel32 = static_cast(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; + } + + return Label{jmp_target_offset}; + } // Instructions void add(Reg32 dst, Imm32 imm32) { @@ -68,6 +89,13 @@ class Assembler { emit(Imm32{static_cast(label.offset - assembled_.size() - kInstructionSize)}); } + void jmp(UnlinkedLabel &label) { + // JMP rel32 + emit(0xe9); + label.patch_offsets.push_back(assembled_.size()); + emit(Imm32{0xdeadbeef}); + } + void mov(Reg32 dst, Imm32 imm32) { emit(0xb8 + register_index(dst).value()); emit(imm32); diff --git a/azm/amd64/assembler_test.cpp b/azm/amd64/assembler_test.cpp index 6c707e7a..867c56b6 100644 --- a/azm/amd64/assembler_test.cpp +++ b/azm/amd64/assembler_test.cpp @@ -73,6 +73,38 @@ int main() { }); }); + s.add_test("JMP, forwards", [](etest::IActions &a) { + Assembler assembler; + + auto slot1 = assembler.unlinked_label(); + assembler.jmp(slot1); + assembler.ud2(); + assembler.jmp(slot1); + auto slot2 = assembler.link(slot1); + assembler.jmp(slot2); + + 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; diff --git a/azm/azm_example.cpp b/azm/azm_example.cpp index ef385425..ad64a360 100644 --- a/azm/azm_example.cpp +++ b/azm/azm_example.cpp @@ -11,6 +11,8 @@ 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}); @@ -18,7 +20,7 @@ int main() { auto end = assembler.label(); assembler.ret(); - // TODO(robinlinden): jump here. + assembler.link(forward); assembler.mov(Reg32::Edx, Imm32{0x12345678}); assembler.mov(Reg32::Ebx, Imm32{0x1234}); assembler.jmp(end); From 29805c1d430e31b3de220f964e6d1f6169191dbe Mon Sep 17 00:00:00 2001 From: Robin Linden Date: Wed, 6 Dec 2023 00:14:07 +0100 Subject: [PATCH 3/3] azm: Simplify label usage --- azm/amd64/assembler.h | 46 +++++++++++++++++++++++------------- azm/amd64/assembler_test.cpp | 4 ++-- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/azm/amd64/assembler.h b/azm/amd64/assembler.h index fefe4228..0934586b 100644 --- a/azm/amd64/assembler.h +++ b/azm/amd64/assembler.h @@ -5,11 +5,13 @@ #ifndef AZM_AMD64_ASSEMBLER_H_ #define AZM_AMD64_ASSEMBLER_H_ +#include #include #include #include #include #include +#include #include namespace azm::amd64 { @@ -40,11 +42,17 @@ constexpr std::optional register_index(Reg32 reg) { } struct Label { - std::size_t offset{}; -}; + struct Linked { + std::size_t offset{}; + }; + struct Unlinked { + std::vector patch_offsets{}; + }; + + static Label linked(std::size_t jmp_target_offset) { return {Linked{jmp_target_offset}}; } + static Label unlinked() { return {Unlinked{}}; } -struct UnlinkedLabel { - std::vector patch_offsets{}; + std::variant v; }; // https://www.felixcloutier.com/x86/ @@ -52,14 +60,16 @@ class Assembler { public: [[nodiscard]] std::vector take_assembled() { return std::exchange(assembled_, {}); } - Label label() const { return Label{assembled_.size()}; } - UnlinkedLabel unlinked_label() const { return UnlinkedLabel{}; } + Label label() const { return Label::linked(assembled_.size()); } + Label unlinked_label() const { return Label::unlinked(); } - Label link(UnlinkedLabel const &label) { + void link(Label &label) { + assert(std::holds_alternative(label.v)); static constexpr int kInstructionSize = 4; std::size_t const jmp_target_offset = assembled_.size(); - for (std::size_t patch_offset : label.patch_offsets) { + auto const &unlinked = std::get(label.v); + for (std::size_t patch_offset : unlinked.patch_offsets) { auto const rel32 = static_cast(jmp_target_offset - patch_offset - kInstructionSize); assembled_[patch_offset + 0] = rel32 & 0xff; assembled_[patch_offset + 1] = (rel32 >> 8) & 0xff; @@ -67,7 +77,7 @@ class Assembler { assembled_[patch_offset + 3] = (rel32 >> 24) & 0xff; } - return Label{jmp_target_offset}; + label = Label::linked(jmp_target_offset); } // Instructions @@ -82,17 +92,19 @@ class Assembler { emit(imm32); } - void jmp(Label label) { + void jmp(Label &label) { // JMP rel32 - emit(0xe9); - static constexpr int kInstructionSize = 4; - emit(Imm32{static_cast(label.offset - assembled_.size() - kInstructionSize)}); - } + if (std::holds_alternative(label.v)) { + auto const &linked = std::get(label.v); + static constexpr int kInstructionSize = 4; + emit(0xe9); + emit(Imm32{static_cast(linked.offset - assembled_.size() - kInstructionSize)}); + return; + } - void jmp(UnlinkedLabel &label) { - // JMP rel32 + auto &unlinked = std::get(label.v); emit(0xe9); - label.patch_offsets.push_back(assembled_.size()); + unlinked.patch_offsets.push_back(assembled_.size()); emit(Imm32{0xdeadbeef}); } diff --git a/azm/amd64/assembler_test.cpp b/azm/amd64/assembler_test.cpp index 867c56b6..778a81dc 100644 --- a/azm/amd64/assembler_test.cpp +++ b/azm/amd64/assembler_test.cpp @@ -80,8 +80,8 @@ int main() { assembler.jmp(slot1); assembler.ud2(); assembler.jmp(slot1); - auto slot2 = assembler.link(slot1); - assembler.jmp(slot2); + assembler.link(slot1); + assembler.jmp(slot1); a.expect_eq(assembler.take_assembled(), CodeVec{