diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ebd279..ceb9b51 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,10 +2,10 @@ name: ci.yml on: push: - branches: - - master + branches: ['master', 'next'] pull_request: types: [ opened, synchronize, reopened ] + branches: ['master', 'next'] jobs: linux-x86: @@ -19,10 +19,57 @@ jobs: sudo apt-get install -y cmake g++ - name: Configure - run: cmake . -B build + run: cmake -B build - name: Build run: cmake --build build - name: Test x86 run: cd build && ./test + + linux-aarch64: + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v4 + + - name: Dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake g++ + + - name: Configure + run: cmake -B build + + - name: Build + run: cmake --build build + + - name: Test AArch64 + run: cd build && ./test + + windows-x86: + runs-on: windows-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + + - name: Setup MSYS2 + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + + - name: Dependencies + shell: msys2 {0} + run: pacman -S git mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-python --noconfirm + + - name: Configure + shell: msys2 {0} + run: cmake -B build -G Ninja -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ + + - name: Build + shell: msys2 {0} + run: cmake --build build + + - name: Test + shell: msys2 {0} + run: cd build && ./test.exe \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index c2575e6..d3a6b16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,19 +24,19 @@ endif() FetchContent_Declare( vstl GIT_REPOSITORY https://github.com/magistermaks/lib-vstl - GIT_TAG 8e590538f66e21aca2558c3029a275725a8f2be3 + GIT_TAG c7f1069ea12fbe8087067841c491940fc3f6dc7f ) FetchContent_MakeAvailable(vstl) set(ASMIO_WRITERS - src/asm/x86/writer.hpp - src/asm/aarch64/writer.hpp + src/asmio/x86/writer.hpp + src/asmio/aarch64/writer.hpp ) set(ASMIO_BRIDGES "${ASMIO_WRITERS}") -list(TRANSFORM ASMIO_BRIDGES REPLACE "src/asm/(.*)/writer.hpp" "${PROJECT_BINARY_DIR}/src/generated/\\1.hpp") +list(TRANSFORM ASMIO_BRIDGES REPLACE "src/asmio/(.*)/writer.hpp" "${PROJECT_BINARY_DIR}/src/generated/\\1.hpp") list(TRANSFORM ASMIO_WRITERS PREPEND "${PROJECT_SOURCE_DIR}/") add_custom_command( @@ -70,7 +70,8 @@ add_executable(test test/elf.cpp ) target_link_libraries(test PRIVATE asmiov) -target_include_directories(test PRIVATE ${ASMIOV_INCLUDE_DIRS} ${vstl_SOURCE_DIR}) +target_include_directories(test PRIVATE ${ASMIOV_INCLUDE_DIRS}) +target_link_libraries(test PRIVATE vstl) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_compile_options(asmiov PUBLIC diff --git a/README.md b/README.md index e0dfbf6..e2573d5 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,7 @@ code generation using the build-in C++ API with syntax closely matching the sour The project consists of a `tasml` CLI assembler and a `test` unit test runner. The code unique to the CLI utility is separated from the rest of -the library and located in the `/src/tasml`. The `/src/file` contains binary file IO, and `/src/asm` -implements the assembler itself. +the library (located in `/src/asmio`) and located in the `/src/tasml`. ## Utility The example of the syntax used by `tasml` utility, the API version of the exact same code can be seen below. diff --git a/src/asm/aarch64/argument/shift.hpp b/src/asm/aarch64/argument/shift.hpp deleted file mode 100644 index 8d135be..0000000 --- a/src/asm/aarch64/argument/shift.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "external.hpp" - -enum struct ShiftType : uint8_t { - LSL = 0b00, ///< shift left - LSR = 0b01, ///< shift right - ASR = 0b10, ///< arithmetic shift right - ROR = 0b11 ///< rotate right -}; \ No newline at end of file diff --git a/src/asm/aarch64/argument/sizing.hpp b/src/asm/aarch64/argument/sizing.hpp deleted file mode 100644 index 8c15b36..0000000 --- a/src/asm/aarch64/argument/sizing.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -enum struct Sizing : uint8_t { - UB = 0b000, ///< add unsigned byte - UH = 0b001, ///< add unsigned word - UW = 0b010, ///< add unsigned dword - UX = 0b011, ///< add unsinged qword - SB = 0b100, ///< add signed byte - SH = 0b101, ///< add signed word - SW = 0b110, ///< add signed dword - SX = 0b111, ///< add signed qword -}; \ No newline at end of file diff --git a/src/asm/aarch64/instructions/basic.cpp b/src/asm/aarch64/instructions/basic.cpp deleted file mode 100644 index 375ca80..0000000 --- a/src/asm/aarch64/instructions/basic.cpp +++ /dev/null @@ -1,552 +0,0 @@ -#include "../writer.hpp" -#include "out/buffer/linkage.hpp" - -namespace asmio::arm { - - /* - * class BufferWriter - */ - - void BufferWriter::put_adc(Registry dst, Registry a, Registry b) { - put_inst_adc(dst, a, b, false); - } - - void BufferWriter::put_adcs(Registry dst, Registry a, Registry b) { - put_inst_adc(dst, a, b, true); - } - - void BufferWriter::put_add(Registry dst, Registry a, Registry b, Sizing size, uint8_t lsl3) { - put_inst_extended_register(0b0'0'01011001, dst, a, b, size, lsl3, false); - } - - void BufferWriter::put_adds(Registry dst, Registry a, Registry b, Sizing size, uint8_t lsl3) { - put_inst_extended_register(0b0'0'01011001, dst, a, b, size, lsl3, true); - } - - void BufferWriter::put_adr(Registry destination, Label label) { - buffer.add_linkage(label, LinkageType::AARCH64_21_5_LO_HI); - put_dword(0b0 << 31 | 0b10000 << 24 | destination.reg); - } - - void BufferWriter::put_adrp(Registry destination, Label label) { - buffer.add_linkage(label, LinkageType::AARCH64_21_5_LO_HI); - put_dword(0b1 << 31 | 0b10000 << 24 | destination.reg); - } - - void BufferWriter::put_movz(Registry registry, uint16_t imm, uint16_t shift) { - put_inst_mov(registry, 0b10100101, imm, shift); - } - - void BufferWriter::put_movk(Registry registry, uint16_t imm, uint16_t shift) { - put_inst_mov(registry, 0b11100101, imm, shift); - } - - void BufferWriter::put_movn(Registry registry, uint16_t imm, uint16_t shift) { - put_inst_mov(registry, 0b00100101, imm, shift); - } - - void BufferWriter::put_mov(Registry dst, uint64_t imm) { - - if (dst.is(Registry::ZERO)) { - return; // do nothing - } - - if (imm <= UINT16_MAX) { - put_movz(dst, imm); - return; - } - - const uint64_t inv = ~imm; - - if (inv <= UINT16_MAX) { - put_movn(dst, inv); - return; - } - - const auto nrs = BitPattern::try_pack(imm); - - if (nrs.ok()) { - return put_orr(dst, dst.wide() ? XZR : WZR, nrs); - } - - const size_t length = dst.wide() ? 64 : 32; - - // TODO this can be made better by using movn/movz strategically here - put_movz(dst, imm & UINT16_MAX); - - for (size_t i = 16; i < length; i += 16) { - imm >>= 16; - uint16_t part = imm & UINT16_MAX; - - if (part) { - put_movk(dst, part, i); - } - } - - } - - void BufferWriter::put_mov(Registry dst, Registry src) { - - if (src.is(Registry::STACK) || dst.is(Registry::STACK)) { - - // when dealing with SP zero can't be used - if (src.is(Registry::ZERO) || dst.is(Registry::ZERO)) { - throw std::runtime_error {"Invalid operands, zero registry can't be used int this context"}; - } - - put_add(dst, src, XZR); - return; - } - - put_orr(dst, src, dst.wide() ? XZR : WZR); - } - - void BufferWriter::put_ret() { - put_ret(LR); - } - - void BufferWriter::put_ret(Registry registry) { - - if (!registry.wide()) { - throw std::runtime_error {"Invalid operand, non-qword register can't be used here"}; - } - - if (!registry.is(Registry::GENERAL)) { - throw std::runtime_error {"Invalid operand, expected general purpose register"}; - } - - put_dword(0b1101011001011111000000'00000'00000 | registry.reg << 5); - } - - void BufferWriter::put_rbit(Registry dst, Registry src) { - - if (dst.wide() != src.wide()) { - throw std::runtime_error {"Invalid operands, both registers need to be of the same size"}; - } - - uint16_t sf = dst.wide() ? 1 : 0; - put_dword(sf << 31 | 0b1011010110 << 21 | src.reg << 5 | dst.reg); - } - - void BufferWriter::put_clz(Registry dst, Registry src) { - put_inst_count(dst, src, 0); - } - - void BufferWriter::put_cls(Registry dst, Registry src) { - put_inst_count(dst, src, 1); - } - - void BufferWriter::put_ldr(Registry registry, Label label) { - uint16_t sf = registry.wide() ? 1 : 0; - buffer.add_linkage(label, LinkageType::AARCH64_19_5_ALIGNED); - put_dword(sf << 30 | 0b011000 << 24 | registry.reg); - } - - void BufferWriter::put_ldri(Registry dst, Registry base, int64_t offset, Sizing sizing) { - put_inst_ldst(dst, base, offset, sizing, POST, LOAD); - } - - void BufferWriter::put_ildr(Registry dst, Registry base, int64_t offset, Sizing sizing) { - put_inst_ldst(dst, base, offset, sizing, PRE, LOAD); - } - - void BufferWriter::put_ldr(Registry dst, Registry base, uint64_t offset, Sizing sizing) { - put_inst_ldst(dst, base, std::bit_cast(offset), sizing, OFFSET, LOAD); - } - - void BufferWriter::put_stri(Registry dst, Registry base, int64_t offset, Sizing sizing) { - put_inst_ldst(dst, base, offset, sizing, POST, STORE); - } - - void BufferWriter::put_istr(Registry dst, Registry base, int64_t offset, Sizing sizing) { - put_inst_ldst(dst, base, offset, sizing, PRE, STORE); - } - - void BufferWriter::put_str(Registry dst, Registry base, uint64_t offset, Sizing sizing) { - put_inst_ldst(dst, base, std::bit_cast(offset), sizing, OFFSET, STORE); - } - - void BufferWriter::put_ands(Registry dst, Registry src, BitPattern pattern) { - - if (!src.is(Registry::GENERAL) || !dst.is(Registry::GENERAL)) { - throw std::runtime_error {"Invalid operand, expected general purpose register"}; - } - - put_inst_bitmask_immediate(0b11'100100, dst, src, pattern); - } - - void BufferWriter::put_and(Registry dst, Registry src, BitPattern pattern) { - - if (!src.is(Registry::GENERAL) || !dst.is(Registry::GENERAL)) { - throw std::runtime_error {"Invalid operand, expected general purpose register"}; - } - - put_inst_bitmask_immediate(0b11'100100, dst, src, pattern); - } - - void BufferWriter::put_ands(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t imm6) { - put_inst_shifted_register(0b1101010, 0, dst, a, b, imm6, shift); - } - - void BufferWriter::put_and(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t imm6) { - put_inst_shifted_register(0b0001010, 0, dst, a, b, imm6, shift); - } - - void BufferWriter::put_eor(Registry dst, Registry src, BitPattern pattern) { - - if (!src.is(Registry::GENERAL) || !dst.is(Registry::GENERAL)) { - throw std::runtime_error {"Invalid operand, expected general purpose register"}; - } - - put_inst_bitmask_immediate(0b10'100100, dst, src, pattern); - } - - void BufferWriter::put_eor(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t imm6) { - put_inst_shifted_register(0b1001010, 0, dst, a, b, imm6, shift); - } - - void BufferWriter::put_orr(Registry destination, Registry source, BitPattern pattern) { - - // destination can be SP - if (!source.is(Registry::GENERAL)) { - throw std::runtime_error {"Invalid operand, expected source to be a general purpose register"}; - } - - put_inst_bitmask_immediate(0b01100100, destination, source, pattern); - } - - void BufferWriter::put_orr(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t imm6) { - put_inst_shifted_register(0b0101010, 0, dst, a, b, imm6, shift); - } - - void BufferWriter::put_svc(uint16_t imm16) { - put_dword(0b11010100000 << 21 | imm16 << 5 | 0b00001); - } - - void BufferWriter::put_sbc(Registry dst, Registry a, Registry b) { - put_inst_sbc(dst, a, b, false); - } - - void BufferWriter::put_sbcs(Registry dst, Registry a, Registry b) { - put_inst_sbc(dst, a, b, true); - } - - void BufferWriter::put_sub(Registry dst, Registry a, Registry b, Sizing size, uint8_t lsl3) { - put_inst_extended_register(0b1'0'01011001, dst, a, b, size, lsl3, false); - } - - void BufferWriter::put_subs(Registry dst, Registry a, Registry b, Sizing size, uint8_t lsl3) { - put_inst_extended_register(0b1'0'01011001, dst, a, b, size, lsl3, true); - } - - void BufferWriter::put_cmp(Registry a, Registry b, Sizing size, uint8_t lsl3) { - put_subs(a.wide() ? XZR : WZR, a, b, size, lsl3); - } - - void BufferWriter::put_cmn(Registry a, Registry b, Sizing size, uint8_t lsl3) { - put_adds(a.wide() ? XZR : WZR, a, b, size, lsl3); - } - - void BufferWriter::put_madd(Registry dst, Registry a, Registry b, Registry addend) { - assert_register_triplet(a, b, dst); - - // we have four register so the last one needs to be checked manually - if (dst.wide() != addend.wide()) { - throw std::runtime_error {"Invalid operands, all given registers need to be of the same width"}; - } - - uint32_t sf = dst.wide() ? 1 : 0; - put_dword(sf << 31 | 0b0011011000 << 21 | b.reg << 16 | addend.reg << 10 | a.reg << 5 | dst.reg); - } - - void BufferWriter::put_smaddl(Registry dst, Registry a, Registry b, Registry addend) { - put_inst_mulopl(dst, a, b, addend, false, false); - } - - void BufferWriter::put_umaddl(Registry dst, Registry a, Registry b, Registry addend) { - put_inst_mulopl(dst, a, b, addend, true, false); - } - - void BufferWriter::put_smsubl(Registry dst, Registry a, Registry b, Registry addend) { - put_inst_mulopl(dst, a, b, addend, false, true); - } - - void BufferWriter::put_umsubl(Registry dst, Registry a, Registry b, Registry addend) { - put_inst_mulopl(dst, a, b, addend, true, true); - } - - void BufferWriter::put_smnegl(Registry dst, Registry a, Registry b) { - put_smsubl(dst, a, b, XZR); - } - - void BufferWriter::put_umnegl(Registry dst, Registry a, Registry b) { - put_umsubl(dst, a, b, XZR); - } - - void BufferWriter::put_mul(Registry dst, Registry a, Registry b) { - put_madd(dst, a, b, dst.wide() ? XZR : WZR); - } - - void BufferWriter::put_smul(Registry dst, Registry a, Registry b) { - put_smaddl(dst, a, b, XZR); - } - - void BufferWriter::put_umul(Registry dst, Registry a, Registry b) { - put_umaddl(dst, a, b, XZR); - } - - void BufferWriter::put_smulh(Registry dst, Registry a, Registry b) { - put_inst_mulh(dst, a, b, false); - } - - void BufferWriter::put_umulh(Registry dst, Registry a, Registry b) { - put_inst_mulh(dst, a, b, true); - } - - void BufferWriter::put_sdiv(Registry dst, Registry a, Registry b) { - put_inst_div(dst, a, b, false); - } - - void BufferWriter::put_udiv(Registry dst, Registry a, Registry b) { - put_inst_div(dst, a, b, true); - } - - void BufferWriter::put_rev16(Registry dst, Registry src) { - put_inst_rev(dst, src, 0b01); - } - - void BufferWriter::put_rev32(Registry dst, Registry src) { - put_inst_rev(dst, src, 0b10); - } - - void BufferWriter::put_rev64(Registry dst, Registry src) { - put_inst_rev(dst, src, 0b11); - } - - void BufferWriter::put_ror(Registry dst, Registry src, Registry bits) { - put_inst_shift_v(dst, src, bits, ShiftType::ROR); - } - - void BufferWriter::put_lsr(Registry dst, Registry src, Registry bits) { - put_inst_shift_v(dst, src, bits, ShiftType::LSR); - } - - void BufferWriter::put_lsr(Registry dst, Registry src, uint16_t shift) { - const uint32_t width = dst.size * 8; - const uint32_t ones = width - 1; // one bit gets discarded anyway - - if (shift > ones) { - throw std::runtime_error {"Invalid operand, can't shift by more than register width"}; - } - - // the ISA doesn't mention us needing to that but for shift=0 - // the top bit would be cut of without any shifting to cover that, - // there were similar issues in LSL (immediate). - if (shift == 0) { - put_mov(dst, src); - return; - } - - put_ubfm(dst, src, {width, ones, shift}); - } - - void BufferWriter::put_lsl(Registry dst, Registry src, Registry bits) { - put_inst_shift_v(dst, src, bits, ShiftType::LSL); - } - - void BufferWriter::put_lsl(Registry dst, Registry src, uint16_t shift) { - const uint32_t width = dst.size * 8; - const uint32_t ones = width - 1; // one bit gets discarded anyway - - if (shift > ones) { - throw std::runtime_error {"Invalid operand, can't shift by more than register width"}; - } - - if (shift == 0) { - put_mov(dst, src); - return; - } - - // TODO we do (width - shift) here while the ISA says to use (ones - shift) - // but that causes the top one bit to not be copied, is that a mistake in the - // specification? As with length set to 64 (for shift 0) the BitPattern would be invalid - // we also need to check for shift=0 and encode this using a normal mov. - // The debugger sees this as the intended LSL alias. - put_ubfm(dst, src, {width, width - shift, -shift % width}); - } - - void BufferWriter::put_asr(Registry dst, Registry src, Registry bits) { - put_inst_shift_v(dst, src, bits, ShiftType::ASR); - } - - void BufferWriter::put_asr(Registry dst, Registry src, uint16_t shift) { - const uint32_t width = dst.size * 8; - const uint32_t ones = width - 1; // one bit gets discarded anyway - - if (shift > ones) { - throw std::runtime_error {"Invalid operand, can't shift by more than register width"}; - } - - // the ISA doesn't mention us needing to that but for shift=0 - // the top bit would be cut of without any shifting to cover that, - // there were similar issues in LSL (immediate). - if (shift == 0) { - put_mov(dst, src); - return; - } - - put_sbfm(dst, src, {width, ones, shift}); - } - - void BufferWriter::put_asl(Registry dst, Registry src, Registry bits) { - put_lsl(dst, src, bits); - } - - void BufferWriter::put_asl(Registry dst, Registry src, uint16_t bits) { - put_lsl(dst, src, bits); - } - - void BufferWriter::put_ror(Registry dst, Registry src, uint8_t imm5) { - put_extr(dst, src, src, imm5); - } - - void BufferWriter::put_extr(Registry dst, Registry low, Registry high, uint8_t imm5) { - assert_register_triplet(dst, low, high); - const uint8_t max_shift = dst.wide() ? 63 : 31; - - if (imm5 > max_shift) { - throw std::runtime_error {"Invalid operands, shift value too large for this context"}; - } - - const uint16_t sf = dst.wide() ? 1 : 0; - put_dword(sf << 31 | 0b00100111 << 23 | sf << 22 | low.reg << 16 | imm5 << 10 | high.reg << 5 | dst.reg); - } - - void BufferWriter::put_csel(Condition condition, Registry dst, Registry truthy, Registry falsy) { - put_inst_csinc(condition, dst, truthy, falsy, false); - } - - void BufferWriter::put_csinc(Condition condition, Registry dst, Registry truthy, Registry falsy) { - put_inst_csinc(condition, dst, truthy, falsy, true); - } - - void BufferWriter::put_cinc(Condition condition, Registry dst, Registry src) { - put_csinc(invert(condition), dst, src, src); - } - - void BufferWriter::put_cinc(Condition condition, Registry dst) { - put_csinc(invert(condition), dst, dst, dst); - } - - void BufferWriter::put_cset(Condition condition, Registry dst) { - put_cinc(condition, dst, dst.wide() ? XZR : WZR); - } - - void BufferWriter::put_tst(Registry a, Registry b, ShiftType shift, uint8_t lsl6) { - put_ands(a.wide() ? XZR : WZR, a, b, shift, lsl6); - } - - void BufferWriter::put_sbfm(Registry dst, Registry src, BitPattern pattern) { - - if (!src.is(Registry::GENERAL) || !dst.is(Registry::GENERAL)) { - throw std::runtime_error {"Invalid operand, expected general purpose register"}; - } - - put_inst_bitmask_immediate(0b00'100110, dst, src, pattern); - } - - void BufferWriter::put_ubfm(Registry dst, Registry src, BitPattern pattern) { - - if (!src.is(Registry::GENERAL) || !dst.is(Registry::GENERAL)) { - throw std::runtime_error {"Invalid operand, expected general purpose register"}; - } - - put_inst_bitmask_immediate(0b10'100110, dst, src, pattern); - } - - void BufferWriter::put_uxtb(Registry dst, Registry src) { - put_ubfm(dst, src, 0xFF); - } - - void BufferWriter::put_uxth(Registry dst, Registry src) { - put_ubfm(dst, src, 0xFFFF); - } - - void BufferWriter::put_bfm(Registry dst, Registry src, BitPattern pattern) { - - if (!src.is(Registry::GENERAL) || !dst.is(Registry::GENERAL)) { - throw std::runtime_error {"Invalid operand, expected general purpose register"}; - } - - put_inst_bitmask_immediate(0b01'100110, dst, src, pattern); - } - - void BufferWriter::put_bfc(Registry dst, BitPattern pattern) { - put_bfm(dst, dst.wide() ? XZR : WZR, pattern); - } - - void BufferWriter::put_bic(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t lsl6) { - put_inst_bic(dst, a, b, shift, lsl6, false); - } - - void BufferWriter::put_bics(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t lsl6) { - put_inst_bic(dst, a, b, shift, lsl6, true); - } - - void BufferWriter::put_hint(uint8_t imm7) { - put_dword(0b1101010100'0'00'011'0010 << 12 | (0b1111'111 & imm7) << 5 | 0b11111); - } - - void BufferWriter::put_hlt(uint16_t imm16) { - put_dword(0b11010100'010 << 21 | imm16 << 5 | 0b000'00); - } - - void BufferWriter::put_hvc(uint16_t imm16) { - put_dword(0b11010100'000 << 21 | imm16 << 5 | 0b000'10); - } - - void BufferWriter::put_smc(uint16_t imm16) { - put_dword(0b11010100'000 << 21 | imm16 << 5 | 0b000'11); - } - - void BufferWriter::put_brk(uint16_t imm) { - put_dword(0b11010100'001 << 21 | imm << 5 | 0b00000); - } - - void BufferWriter::put_isb() { - put_dword(0b1101010100'0'00'011'0011 << 12 | 0b1111 << 8 | 0b1'10'11111); - } - - void BufferWriter::put_nop() { - put_hint(0b0000'000); - } - - void BufferWriter::put_yield() { - put_hint(0b0000'001); - } - - void BufferWriter::put_wfe() { - put_hint(0b0000'010); - } - - void BufferWriter::put_wfi() { - put_hint(0b0000'011); - } - - void BufferWriter::put_sev() { - put_hint(0b0000'100); - } - - void BufferWriter::put_sevl() { - put_hint(0b0000'101); - } - - void BufferWriter::put_esb() { - put_hint(0b0010'000); - } - - void BufferWriter::put_psb() { - put_hint(0b0010'001); - } - -} diff --git a/src/asm/aarch64/writer.hpp b/src/asm/aarch64/writer.hpp deleted file mode 100644 index d326724..0000000 --- a/src/asm/aarch64/writer.hpp +++ /dev/null @@ -1,210 +0,0 @@ -#pragma once -#include - -#include "argument/sizing.hpp" -#include "argument/registry.hpp" -#include "argument/shift.hpp" -#include "argument/condition.hpp" -#include "argument/pattern.hpp" - -namespace asmio::arm { - - class BufferWriter : public BasicBufferWriter { - - private: - - enum MemoryOperation : uint8_t { - POST = 0b01, - PRE = 0b11, - OFFSET = 0b00, - }; - - enum MemoryDirection : uint8_t { - LOAD = 0b11, - STORE = 0b00, - }; - - /** - * Writes a standard 'bitmask immediate' instruction into the buffer, - * with 'sf' derived from destination size. - */ - void put_inst_bitmask_immediate(uint32_t opc_from_23, Registry destination, Registry source, BitPattern pattern); - - /** - * Writes the standard 'shifted register' instruction into the buffer, - * with 'sf' derived from destination size. - */ - void put_inst_shifted_register(uint32_t opc_from_24, uint32_t bit_21, Registry dst, Registry n, Registry m, uint8_t imm6, ShiftType shift); - - /** - * Writes the standard 'extended register' instruction into the buffer, - * with 'sf' derived from destination size. This command accepts SP as destination only when set_flags is false. - */ - void put_inst_extended_register(uint32_t opcode_from_21, Registry destination, Registry a, Registry b, Sizing add, uint8_t imm3, bool set_flags); - - protected: - - static uint8_t pack_shift(uint8_t shift, bool wide); - static uint64_t get_size(Size size); - void assert_register_triplet(Registry a, Registry b, Registry c); - - /// Encode generic, 16 bit, immediate move, used by MOVN, MOVK, MOVZ - void put_inst_mov(Registry registry, uint16_t opc, uint16_t imm, uint16_t shift); - - /// Encode ORR instruction, using the given N:R:S fields - void put_inst_orr_bitmask(Registry destination, Registry source, uint16_t n_immr_imms); - - /// Encode "ADC/ADCS (extended register)" operation - void put_inst_adc(Registry destination, Registry a, Registry b, bool set_flags); - - /// Encode "SBC/SBCS" operation - void put_inst_sbc(Registry destination, Registry a, Registry b, bool set_flags); - - /// Encode "BIC/BICS" operation - void put_inst_bic(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t lsl6, bool set_flags); - - /// Encode "CLS/CLZ" operation - void put_inst_count(Registry destination, Registry source, uint8_t imm1); - - /// Encode "ILDR/LDRI/LDR" as well as the "ISTR/STRI/STR" operations - void put_inst_ldst(Registry dst, Registry base, int64_t offset, Sizing sizing, MemoryOperation op, MemoryDirection dir); - - /// Encode "SMADDL/UMADDL/SMSUBL/UMSUBL" operation - void put_inst_mulopl(Registry dst, Registry a, Registry b, Registry addend, bool is_unsigned, bool is_subtract); - - /// Encode "UMULH/SMULH" operation - void put_inst_mulh(Registry dst, Registry a, Registry b, bool is_unsigned); - - /// Encode "UDIV/UDIV" operation - void put_inst_div(Registry dst, Registry a, Registry b, bool is_unsigned); - - /// Encode "REV16/REV32/REV64" operation - void put_inst_rev(Registry dst, Registry src, uint16_t size_opc_10); - - /// Encode "LSL/LSR/ROR" operation - void put_inst_shift_v(Registry dst, Registry src, Registry bits, ShiftType shift); - - /// Encode "CSINC/CSEL/CSET/CINC" operation - void put_inst_csinc(Condition condition, Registry dst, Registry truthy, Registry falsy, bool increment_truth); - - public: - - void put_inst_add_imm(Registry destination, Registry source, uint16_t imm12, bool lsl_12 = false, bool set_flags = false); - void put_inst_add_shifted(Registry destination, Registry a, Registry b, ShiftType shift, uint8_t imm6, bool set_flags = false); - - public: - - BufferWriter(SegmentedBuffer& buffer); - - // basic - INST put_adc(Registry dst, Registry a, Registry b); ///< Add with carry - INST put_adcs(Registry dst, Registry a, Registry b); ///< Add with carry and set flags - INST put_add(Registry dst, Registry a, Registry b, Sizing size = Sizing::UX, uint8_t lsl3 = 0); ///< Add two registers, potentially extending one of them - INST put_adds(Registry dst, Registry a, Registry b, Sizing size = Sizing::UX, uint8_t lsl3 = 0); ///< Add two registers, set the flags, potentially extending one of them - INST put_adr(Registry destination, Label label); ///< Form a PC-relative address - INST put_adrp(Registry destination, Label label); ///< Form a PC-page-relative address - INST put_movz(Registry dst, uint16_t imm, uint16_t shift = 0); ///< Move shifted WORD into register, zero other bits - INST put_movk(Registry dst, uint16_t imm, uint16_t shift = 0); ///< Move shifted WORD into register, keep other bits - INST put_movn(Registry dst, uint16_t imm, uint16_t shift = 0); ///< Move shifted WORD into register, zero other bits, then NOT the register - INST put_mov(Registry dst, uint64_t imm); ///< Move immediate into register - INST put_mov(Registry dst, Registry src); ///< Move value between registers - INST put_ret(); ///< Return from procedure using link register - INST put_ret(Registry src); ///< Return from procedure - INST put_rbit(Registry dst, Registry src); ///< Reverse bits - INST put_clz(Registry dst, Registry src); ///< Count leading zeros - INST put_cls(Registry dst, Registry src); ///< Count leading signs (ones) - INST put_ldr(Registry registry, Label label); ///< Load value from memory - INST put_ildr(Registry dst, Registry base, int64_t offset, Sizing size); ///< Increment base and load value from memory - INST put_ldri(Registry dst, Registry base, int64_t offset, Sizing size); ///< Load value from memory and increment base - INST put_ldr(Registry registry, Registry base, uint64_t offset, Sizing size); ///< Load value from memory - INST put_istr(Registry dst, Registry base, int64_t offset, Sizing size); ///< Increment base and store value to memory - INST put_stri(Registry dst, Registry base, int64_t offset, Sizing size); ///< Store value to memory and increment base - INST put_str(Registry registry, Registry base, uint64_t offset, Sizing size); ///< Store value to memory - INST put_ands(Registry dst, Registry src, BitPattern pattern); ///< Bitwise AND between register and bit pattern - INST put_and(Registry dst, Registry src, BitPattern pattern); ///< Bitwise AND between register and bit pattern, set flags - INST put_ands(Registry dst, Registry a, Registry b, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Bitwise AND between two register, shifting the second one, set flags - INST put_and(Registry dst, Registry a, Registry b, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Bitwise AND between two register, shifting the second one - INST put_eor(Registry destination, Registry source, BitPattern pattern); ///< Bitwise XOR between register and bit pattern - INST put_eor(Registry dst, Registry a, Registry b, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Bitwise XOR between two register, shifting the second one - INST put_orr(Registry destination, Registry source, BitPattern pattern); ///< Bitwise OR between register and bit pattern - INST put_orr(Registry dst, Registry a, Registry b, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Bitwise OR between two register, shifting the second one - INST put_sbc(Registry dst, Registry a, Registry b); ///< Subtract with Carry - INST put_sbcs(Registry dst, Registry a, Registry b); ///< Subtract with Carry and set flags - INST put_sub(Registry dst, Registry a, Registry b, Sizing size = Sizing::UX, uint8_t lsl3 = 0); ///< Add two registers, potentially extending one of them - INST put_subs(Registry dst, Registry a, Registry b, Sizing size = Sizing::UX, uint8_t lsl3 = 0); ///< Add two registers, set the flags, potentially extending one of them - INST put_cmp(Registry a, Registry b, Sizing size = Sizing::UX, uint8_t lsl3 = 0); ///< Compare - INST put_cmn(Registry a, Registry b, Sizing size = Sizing::UX, uint8_t lsl3 = 0); ///< Compare negative - INST put_madd(Registry dst, Registry a, Registry b, Registry addend); ///< Multiply and Add 64 bit registers - INST put_smaddl(Registry dst, Registry a, Registry b, Registry addend); ///< Signed multiply two 32 bit registers and add 64 bit register - INST put_umaddl(Registry dst, Registry a, Registry b, Registry addend); ///< Unsigned multiply two 32 bit registers and add 64 bit register - INST put_smsubl(Registry dst, Registry a, Registry b, Registry addend); ///< Signed multiply two 32 bit registers and subtract 64 bit register - INST put_umsubl(Registry dst, Registry a, Registry b, Registry addend); ///< Unsigned multiply two 32 bit registers and subtract 64 bit register - INST put_smnegl(Registry dst, Registry a, Registry b); ///< Signed multiply two 32 bit registers and negate result - INST put_umnegl(Registry dst, Registry a, Registry b); ///< Unsigned multiply two 32 bit registers and negate result - INST put_mul(Registry dst, Registry a, Registry b); ///< Multiply 64 bit registers - INST put_smul(Registry dst, Registry a, Registry b); ///< Signed multiply 32 bit registers - INST put_umul(Registry dst, Registry a, Registry b); ///< Unsigned multiply 32 bit registers - INST put_smulh(Registry dst, Registry a, Registry b); ///< Signed multiply high - INST put_umulh(Registry dst, Registry a, Registry b); ///< Unsigned multiply high - INST put_sdiv(Registry dst, Registry a, Registry b); ///< Signed Divide - INST put_udiv(Registry dst, Registry a, Registry b); ///< Unsigned Divide - INST put_rev16(Registry dst, Registry src); ///< Reverse bytes in 16-bit words - INST put_rev32(Registry dst, Registry src); ///< Reverse bytes in 32-bit dwords - INST put_rev64(Registry dst, Registry src); ///< Reverse bytes in 64-bit qwords - INST put_ror(Registry dst, Registry src, Registry bits); ///< Rotate Right by register - INST put_lsr(Registry dst, Registry src, Registry bits); ///< Logical Shift Right by register - INST put_lsr(Registry dst, Registry src, uint16_t bits); ///< Logical Shift Right by immediate - INST put_lsl(Registry dst, Registry src, Registry bits); ///< Logical Shift Left by register - INST put_lsl(Registry dst, Registry src, uint16_t bits); ///< Logical Shift Left by immediate - INST put_asr(Registry dst, Registry src, Registry bits); ///< Arithmetic Shift Right by register - INST put_asr(Registry dst, Registry src, uint16_t bits); ///< Arithmetic Shift Right by immediate - INST put_asl(Registry dst, Registry src, Registry bits); ///< Arithmetic Shift Left by register - INST put_asl(Registry dst, Registry src, uint16_t bits); ///< Arithmetic Shift Left by immediate - INST put_ror(Registry dst, Registry src, uint8_t imm); ///< Rotate Right by immediate - INST put_extr(Registry dst, Registry left, Registry right, uint8_t imm5); ///< Extract register - INST put_csel(Condition condition, Registry dst, Registry truthy, Registry falsy); ///< Conditional Select - INST put_csinc(Condition condition, Registry dst, Registry truthy, Registry falsy); ///< Conditional Select and Increment if false - INST put_cinc(Condition condition, Registry dst, Registry src);///< Conditional Increment if true - INST put_cinc(Condition condition, Registry dst); ///< Conditional Increment if true - INST put_cset(Condition condition, Registry dst); ///< Conditional Set if true - INST put_tst(Registry a, Registry b, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Test shifted register - INST put_sbfm(Registry dst, Registry src, BitPattern pattern); ///< Signed Bitfield Insert in Zero - INST put_ubfm(Registry dst, Registry src, BitPattern pattern); ///< Unsigned Bitfield Insert in Zero - INST put_uxtb(Registry dst, Registry src); ///< Extract Byte - INST put_uxth(Registry dst, Registry src); ///< Extract Two Bytes - INST put_bfm(Registry dst, Registry src, BitPattern pattern); ///< Bitfield Move - INST put_bfc(Registry dst, BitPattern pattern); ///< Bitfield Clear - INST put_bic(Registry dst, Registry a, Registry b, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Bitwise Bit Clear - INST put_bics(Registry dst, Registry a, Registry b, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Bitwise Bit Clear and set flags - - // control - INST put_svc(uint16_t imm16); ///< Supervisor call - INST put_hvc(uint16_t imm16); ///< Hypervisor Call - INST put_smc(uint16_t imm16); ///< Secure Monitor Call - INST put_hlt(uint16_t imm16); ///< Halt - INST put_brk(uint16_t imm16); ///< Breakpoint Instruction exception - INST put_hint(uint8_t imm7); ///< Architectural hint - INST put_isb(); ///< Instruction Synchronization Barrier - INST put_nop(); ///< No operation - INST put_yield(); ///< Indicate spin-lock - INST put_wfe(); ///< Wait For Event - INST put_wfi(); ///< Wait For Interrupt - INST put_sev(); ///< Send Event - INST put_sevl(); ///< Send Event Local - INST put_esb(); ///< Error Synchronization Barrier - INST put_psb(); ///< Profiling Synchronization Barrier. - - // branch - INST put_b(const Label& label); ///< Branch - INST put_b(Condition condition, const Label& label); ///< Branch conditionally - INST put_bl(const Label& label); ///< Branch with link - INST put_blr(Registry ptr); ///< Branch with link to register - INST put_br(Registry ptr); ///< Branch to register - INST put_cbnz(Registry src, const Label& label); ///< Branch if register is not zero - INST put_cbz(Registry src, const Label& label); ///< Branch if register is zero - INST put_tbz(Registry test, uint16_t bit6, const Label& label); ///< Test bit and Branch if Zero - INST put_tbnz(Registry test, uint16_t bit6, const Label& label); ///< Test bit and Branch if Not Zero - - }; - -} diff --git a/src/asm/aarch64/argument/condition.hpp b/src/asmio/aarch64/argument/condition.hpp similarity index 87% rename from src/asm/aarch64/argument/condition.hpp rename to src/asmio/aarch64/argument/condition.hpp index ae1b600..6848a5f 100644 --- a/src/asm/aarch64/argument/condition.hpp +++ b/src/asmio/aarch64/argument/condition.hpp @@ -1,6 +1,6 @@ #pragma once -#include "external.hpp" +#include namespace asmio::arm { @@ -23,7 +23,7 @@ namespace asmio::arm { */ constexpr Condition invert(Condition condition) { if (condition == Condition::AL) throw std::runtime_error {"The 'always' condition can't be inverted, as there is no 'never' condition!"}; - return (Condition) (uint32_t(condition) ^ 1); + return static_cast(static_cast(condition) ^ 1); } } \ No newline at end of file diff --git a/src/asmio/aarch64/argument/order.hpp b/src/asmio/aarch64/argument/order.hpp new file mode 100644 index 0000000..1611abf --- /dev/null +++ b/src/asmio/aarch64/argument/order.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +namespace asmio::arm { + + // Specifies ordering semantics of instructions, + // learn more about ordering semantics: https://davekilian.com/acquire-release.html + enum struct Order : uint8_t { + NONE = 0b00, ///< Ensure no ordering of operations + RELEASE = 0b01, ///< Ensure that the preceding operations finish before this one starts + ACQUIRE = 0b10, ///< Ensure that the following operations wait for this one to finish + ACQUIRE_RELEASE = 0b11, ///< Ensure that both previous and preceding operations are executed in order + }; + + /** + * Check if Order is ACQUIRE or ACQUIRE_RELEASE + */ + constexpr bool is_order_acquire(Order order) { + return static_cast(order) & static_cast(Order::ACQUIRE); + } + + /** + * Check if Order is RELEASE or ACQUIRE_RELEASE + */ + constexpr bool is_order_release(Order order) { + return static_cast(order) & static_cast(Order::RELEASE); + } + +} \ No newline at end of file diff --git a/src/asm/aarch64/argument/pattern.cpp b/src/asmio/aarch64/argument/pattern.cpp similarity index 99% rename from src/asm/aarch64/argument/pattern.cpp rename to src/asmio/aarch64/argument/pattern.cpp index dce171e..a95b212 100644 --- a/src/asm/aarch64/argument/pattern.cpp +++ b/src/asmio/aarch64/argument/pattern.cpp @@ -1,6 +1,6 @@ #include "pattern.hpp" -#include +#include namespace asmio::arm { diff --git a/src/asm/aarch64/argument/pattern.hpp b/src/asmio/aarch64/argument/pattern.hpp similarity index 96% rename from src/asm/aarch64/argument/pattern.hpp rename to src/asmio/aarch64/argument/pattern.hpp index fbf28ae..c937bc7 100644 --- a/src/asm/aarch64/argument/pattern.hpp +++ b/src/asmio/aarch64/argument/pattern.hpp @@ -1,9 +1,8 @@ #pragma once -#include -#include - -#include "external.hpp" +#include +#include +#include namespace asmio::arm { diff --git a/src/asmio/aarch64/argument/prefetch.hpp b/src/asmio/aarch64/argument/prefetch.hpp new file mode 100644 index 0000000..08f018f --- /dev/null +++ b/src/asmio/aarch64/argument/prefetch.hpp @@ -0,0 +1,81 @@ +#pragma once +#include +#include + +namespace asmio::arm { + + // The Prefetch enum specifies the prefetch hint as follows: + // + // Access type: + // - PLD for prefetch for load data. + // - PLI for prefetch for load instruction. + // - PST for prefetch for store. + // + // Target cache level: + // - L1 for Level 1 cache. + // - L2 for Level 2 cache. + // - L3 for Level 3 cache. + // - SLC for system level cache (requires FEAT_PRFMSLC support). + // + // Policy: + // - KEEP for retained or temporal prefetch, allocated in the cache normally. + // - STRM for streaming or non-temporal prefetch, for data that is used only once. + enum struct Prefetch { + + PLD_L1_KEEP = 0b00000, + PLD_L1_STRM = 0b00001, + PLD_L2_KEEP = 0b00010, + PLD_L2_STRM = 0b00011, + PLD_L3_KEEP = 0b00100, + PLD_L3_STRM = 0b00101, + PLD_SLC_KEEP = 0b00110, + PLD_SLC_STRM = 0b00111, + PLI_L1_KEEP = 0b01000, + PLI_L1_STRM = 0b01001, + PLI_L2_KEEP = 0b01010, + PLI_L2_STRM = 0b01011, + PLI_L3_KEEP = 0b01100, + PLI_L3_STRM = 0b01101, + PLI_SLC_KEEP = 0b01110, + PLI_SLC_STRM = 0b01111, + PST_L1_KEEP = 0b10000, + PST_L1_STRM = 0b10001, + PST_L2_KEEP = 0b10010, + PST_L2_STRM = 0b10011, + PST_L3_KEEP = 0b10100, + PST_L3_STRM = 0b10101, + PST_SLC_KEEP = 0b10110, + PST_SLC_STRM = 0b10111, + + }; + + inline Prefetch parse_prefetch_enum(const std::string_view& view) { + if (view == "PLD_L1_KEEP") return Prefetch::PLD_L1_KEEP; + if (view == "PLD_L1_STRM") return Prefetch::PLD_L1_STRM; + if (view == "PLD_L2_KEEP") return Prefetch::PLD_L2_KEEP; + if (view == "PLD_L2_STRM") return Prefetch::PLD_L2_STRM; + if (view == "PLD_L3_KEEP") return Prefetch::PLD_L3_KEEP; + if (view == "PLD_L3_STRM") return Prefetch::PLD_L3_STRM; + if (view == "PLD_SLC_KEEP") return Prefetch::PLD_SLC_KEEP; + if (view == "PLD_SLC_STRM") return Prefetch::PLD_SLC_STRM; + if (view == "PLI_L1_KEEP") return Prefetch::PLI_L1_KEEP; + if (view == "PLI_L1_STRM") return Prefetch::PLI_L1_STRM; + if (view == "PLI_L2_KEEP") return Prefetch::PLI_L2_KEEP; + if (view == "PLI_L2_STRM") return Prefetch::PLI_L2_STRM; + if (view == "PLI_L3_KEEP") return Prefetch::PLI_L3_KEEP; + if (view == "PLI_L3_STRM") return Prefetch::PLI_L3_STRM; + if (view == "PLI_SLC_KEEP") return Prefetch::PLI_SLC_KEEP; + if (view == "PLI_SLC_STRM") return Prefetch::PLI_SLC_STRM; + if (view == "PST_L1_KEEP") return Prefetch::PST_L1_KEEP; + if (view == "PST_L1_STRM") return Prefetch::PST_L1_STRM; + if (view == "PST_L2_KEEP") return Prefetch::PST_L2_KEEP; + if (view == "PST_L2_STRM") return Prefetch::PST_L2_STRM; + if (view == "PST_L3_KEEP") return Prefetch::PST_L3_KEEP; + if (view == "PST_L3_STRM") return Prefetch::PST_L3_STRM; + if (view == "PST_SLC_KEEP") return Prefetch::PST_SLC_KEEP; + if (view == "PST_SLC_STRM") return Prefetch::PST_SLC_STRM; + + throw std::runtime_error {"Invalid prefetch enumeration"}; + } + +} diff --git a/src/asm/aarch64/argument/registry.hpp b/src/asmio/aarch64/argument/registry.hpp similarity index 97% rename from src/asm/aarch64/argument/registry.hpp rename to src/asmio/aarch64/argument/registry.hpp index 40d805b..fbedf2f 100644 --- a/src/asm/aarch64/argument/registry.hpp +++ b/src/asmio/aarch64/argument/registry.hpp @@ -1,8 +1,8 @@ #pragma once -#include "external.hpp" -#include "../../util.hpp" -#include "out/buffer/sizes.hpp" +#include +#include +#include namespace asmio::arm { diff --git a/src/asmio/aarch64/argument/shift.hpp b/src/asmio/aarch64/argument/shift.hpp new file mode 100644 index 0000000..daf79cd --- /dev/null +++ b/src/asmio/aarch64/argument/shift.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace asmio::arm { + + enum struct ShiftType : uint8_t { + LSL = 0b00, ///< shift left + LSR = 0b01, ///< shift right + ASR = 0b10, ///< arithmetic shift right + ROR = 0b11 ///< rotate right + }; + +} \ No newline at end of file diff --git a/src/asmio/aarch64/argument/sizing.hpp b/src/asmio/aarch64/argument/sizing.hpp new file mode 100644 index 0000000..de35d7b --- /dev/null +++ b/src/asmio/aarch64/argument/sizing.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace asmio::arm { + + enum struct Sizing : uint8_t { + UB = 0b000, ///< unsigned byte + UH = 0b001, ///< unsigned word + UW = 0b010, ///< unsigned dword + UX = 0b011, ///< unsinged qword + SB = 0b100, ///< signed byte + SH = 0b101, ///< signed word + SW = 0b110, ///< signed dword + SX = 0b111, ///< signed qword + }; + +} \ No newline at end of file diff --git a/src/asm/aarch64/module.cpp b/src/asmio/aarch64/module.cpp similarity index 86% rename from src/asm/aarch64/module.cpp rename to src/asmio/aarch64/module.cpp index 93064bb..16b21ea 100644 --- a/src/asm/aarch64/module.cpp +++ b/src/asmio/aarch64/module.cpp @@ -86,6 +86,19 @@ namespace asmio::arm { throw std::runtime_error {"Invalid argument format, expected shift specifier"}; } + template <> + Order parse_argument(TokenStream stream) { + const Token& token = stream.expect(Token::NAME); + std::string raw = util::to_lower(token.raw); + + if (raw == "a") return Order::ACQUIRE; + if (raw == "r") return Order::RELEASE; + if (raw == "ar") return Order::ACQUIRE_RELEASE; + if (raw == "ra") return Order::ACQUIRE_RELEASE; + + throw std::runtime_error {"Invalid argument format, expected order semantics"}; + } + template <> Condition parse_argument(TokenStream stream) { const Token& token = stream.expect(Token::NAME); @@ -111,6 +124,14 @@ namespace asmio::arm { throw std::runtime_error {"Invalid argument format, expected condition specifier"}; } + template <> + Prefetch parse_argument(TokenStream stream) { + const Token& token = stream.expect(Token::NAME); + + // FIXME this one is case sensitive and requires uppercase + return parse_prefetch_enum(token.raw); + } + template <> BitPattern parse_argument(TokenStream stream) { if (const Token* token = stream.accept(Token::INT)) { diff --git a/src/asm/aarch64/module.hpp b/src/asmio/aarch64/module.hpp similarity index 86% rename from src/asm/aarch64/module.hpp rename to src/asmio/aarch64/module.hpp index b82cb46..4644161 100644 --- a/src/asm/aarch64/module.hpp +++ b/src/asmio/aarch64/module.hpp @@ -1,6 +1,6 @@ #pragma once -#include -#include +#include +#include namespace asmio::arm { diff --git a/src/asm/aarch64/writer.cpp b/src/asmio/aarch64/writer.cpp similarity index 80% rename from src/asm/aarch64/writer.cpp rename to src/asmio/aarch64/writer.cpp index 5c6ab36..366cc3c 100644 --- a/src/asm/aarch64/writer.cpp +++ b/src/asmio/aarch64/writer.cpp @@ -66,20 +66,6 @@ namespace asmio::arm { put_dword(sf << 31 | opc << 23 | hw << 21 | imm << 5 | registry.reg); } - void BufferWriter::put_inst_orr_bitmask(Registry destination, Registry source, uint16_t n_immr_imms) { - - // destination can be SP - if (!source.is(Registry::GENERAL)) { - throw std::runtime_error {"Invalid operand, expected source to be a general purpose register."}; - } - - if (destination.wide() != source.wide()) { - throw std::runtime_error {"Invalid operands, all given registers need to be of the same width."}; - } - - put_inst_bitmask_immediate(0b01100100, destination, source, n_immr_imms); - } - void BufferWriter::put_inst_extended_register(uint32_t opcode_from_21, Registry destination, Registry a, Registry b, Sizing add, uint8_t imm3, bool set_flags) { // we can only accept SP as destination IF set_flags is false @@ -109,7 +95,7 @@ namespace asmio::arm { uint16_t sf = destination.wide() ? 1 : 0; uint32_t fb = (set_flags ? 1 : 0) << 29; // S bit - put_dword(sf << 31 | opcode_from_21 << 21 | fb | b.reg << 16 | uint8_t(add) << 13 | imm3 << 10 | a.reg << 5 | destination.reg); + put_dword(sf << 31 | opcode_from_21 << 21 | fb | b.reg << 16 | uint8_t(add) << 13 | (imm3 & 0b111) << 10 | a.reg << 5 | destination.reg); } void BufferWriter::put_inst_adc(Registry destination, Registry a, Registry b, bool set_flags) { @@ -282,7 +268,7 @@ namespace asmio::arm { put_dword(sf << 31 | 0b0011010110 << 21 | bits.reg << 16 | 0b0010 << 12 | uint32_t(shift) << 10 | src.reg << 5 | dst.reg); } - void BufferWriter::put_inst_csinc(Condition condition, Registry dst, Registry truthy, Registry falsy, bool increment_truth) { + void BufferWriter::put_inst_csinc(Condition condition, Registry dst, Registry truthy, Registry falsy, bool increment, bool invert) { assert_register_triplet(dst, truthy, falsy); if (!dst.is(Registry::GENERAL) || !truthy.is(Registry::GENERAL) || !falsy.is(Registry::GENERAL)) { @@ -290,34 +276,81 @@ namespace asmio::arm { } const uint16_t sf = dst.wide() ? 1 : 0; - put_dword(sf << 31 | 0b00'11010100 << 21 | falsy.reg << 16 | uint32_t(condition) << 12 | increment_truth << 10 | truthy.reg << 5 | dst.reg); + put_dword(sf << 31 | invert << 30 | 0b11010100 << 21 | falsy.reg << 16 | uint32_t(condition) << 12 | increment << 10 | truthy.reg << 5 | dst.reg); } - void BufferWriter::put_inst_add_imm(Registry destination, Registry source, uint16_t imm12, bool lsl_12, bool set_flags) { - - if (source.is(Registry::ZERO) || destination.is(Registry::ZERO)) { - throw std::runtime_error {"Invalid operands, zero register can't be used here"}; + void BufferWriter::put_inst_add_imm(Registry dst, Registry src, uint16_t imm12, bool lsl_12, bool set_flags, bool subtract) { + if (imm12 > 0xfff) { // 12 bits + throw std::runtime_error {"Invalid operand, immediate value out of bounds"}; } - if (destination.wide() != source.wide()) { + if (dst.size != src.size) { throw std::runtime_error {"Invalid operands, all given registers need to be of the same width"}; } - uint16_t sf = destination.wide() ? 1 : 0; - uint32_t fb = (set_flags ? 1 : 0) << 29; // S bit - put_dword(sf << 31 | 0b0'0'10001 << 24 | fb | (lsl_12 ? 0b01 : 0x00) << 22 | imm12 << 10 | source.reg << 5 | destination.reg); + if (src.is(Registry::ZERO)) { + throw std::runtime_error {"Invalid operand, zero register can't be used as source"}; + } + + if (!dst.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid operand, destination must be a general purpose register"}; + } + + uint32_t sf = dst.wide() ? 1 : 0; + put_dword(sf << 31 | subtract << 30 | set_flags << 29 | 0b100010 << 23 | lsl_12 << 22 | imm12 << 10 | src.reg << 5 | dst.reg); } - void BufferWriter::put_inst_add_shifted(Registry destination, Registry a, Registry b, ShiftType shift, uint8_t imm6, bool set_flags) { + void BufferWriter::put_inst_add_shifted(Registry destination, Registry a, Registry b, ShiftType shift, uint8_t imm6, bool set_flags, bool subtract) { assert_register_triplet(a, b, destination); + if (!destination.is(Registry::GENERAL) || !a.is(Registry::GENERAL) || !b.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid operands, expected general purpose registers"}; + } + if (shift == ShiftType::ROR) { throw std::runtime_error {"Invalid shift type, ROR shift type is not allowed here"}; } uint32_t sf = destination.wide() ? 1 : 0; - uint32_t fb = (set_flags ? 1 : 0) << 29; // S bit - put_dword(sf << 31 | 0b0'0'01011 << 24 | fb | uint8_t(shift) << 22 | b.reg << 16 | imm6 << 10 | a.reg << 5 | destination.reg); + put_dword(sf << 31 | subtract << 30 | set_flags << 29 | 0b0'0'01011 << 24 | uint8_t(shift) << 22 | b.reg << 16 | (imm6 & 0b111111) << 10 | a.reg << 5 | destination.reg); + } + + void BufferWriter::put_inst_ldstx(Registry r1, Registry r2, Registry status, Registry mem, uint8_t size, bool load, bool barrier, bool pair) { + if (!mem.wide()) { + throw std::runtime_error {"Invalid operand, source register must be wide"}; + } + + if (mem.is(Registry::ZERO)) { + throw std::runtime_error {"Invalid operand, source can't be the zero register"}; + } + + if (pair) { + if (r1.wide() != r2.wide()) { + throw std::runtime_error {"Invalid operands, both destination registers need to be of the same size"}; + } + + if (!r1.is(Registry::GENERAL) || !r2.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid operands, both destination registers need to be general purpose"}; + } + } + + put_dword(size << 30 | 0b0010000 << 23 | load << 22 | pair << 21 | status.reg << 16 | barrier << 15 | r2.reg << 10 | mem.reg << 5 | r1.reg); + } + + void BufferWriter::put_inst_ldst_simm9(Registry dst, Registry src, int16_t offset, uint32_t size, bool load, uint32_t opc) { + if (!src.wide()) { + throw std::runtime_error {"Invalid operand, source register must be wide"}; + } + + if (src.is(Registry::ZERO)) { + throw std::runtime_error {"Invalid operand, source can't be the zero register"}; + } + + if ((offset < -256) || (offset > 255)) { + throw std::runtime_error {"Invalid operand, offset out of valid range"}; + } + + put_dword(size << 30 | 0b111'0'00'00'0 << 21 | load << 22 | (offset & 0x1ff) << 12 | opc << 10 | src.reg << 5 | dst.reg); } } \ No newline at end of file diff --git a/src/asmio/aarch64/writer.hpp b/src/asmio/aarch64/writer.hpp new file mode 100644 index 0000000..6469c3f --- /dev/null +++ b/src/asmio/aarch64/writer.hpp @@ -0,0 +1,364 @@ +#pragma once +#include + +#include "argument/sizing.hpp" +#include "argument/registry.hpp" +#include "argument/shift.hpp" +#include "argument/condition.hpp" +#include "argument/order.hpp" +#include "argument/pattern.hpp" +#include "argument/prefetch.hpp" + +namespace asmio::arm { + + class BufferWriter : public BasicBufferWriter { + + private: + + enum MemoryOperation : uint8_t { + POST = 0b01, + PRE = 0b11, + OFFSET = 0b00, + }; + + enum MemoryDirection : uint8_t { + LOAD = 0b11, + STORE = 0b00, + }; + + /** + * Writes a standard 'bitmask immediate' instruction into the buffer, + * with 'sf' derived from destination size. + */ + void put_inst_bitmask_immediate(uint32_t opc_from_23, Registry destination, Registry source, BitPattern pattern); + + /** + * Writes the standard 'shifted register' instruction into the buffer, + * with 'sf' derived from destination size. + */ + void put_inst_shifted_register(uint32_t opc_from_24, uint32_t bit_21, Registry dst, Registry n, Registry m, uint8_t imm6, ShiftType shift); + + /** + * Writes the standard 'extended register' instruction into the buffer, + * with 'sf' derived from destination size. This command accepts SP as destination only when set_flags is false. + */ + void put_inst_extended_register(uint32_t opcode_from_21, Registry destination, Registry a, Registry b, Sizing add, uint8_t imm3, bool set_flags); + + protected: + + static uint8_t pack_shift(uint8_t shift, bool wide); + + void assert_register_triplet(Registry a, Registry b, Registry c); + + /// Encode generic, 16 bit, immediate move, used by MOVN, MOVK, MOVZ + void put_inst_mov(Registry registry, uint16_t opc, uint16_t imm, uint16_t shift); + + /// Encode "ADC/ADCS (extended register)" operation + void put_inst_adc(Registry destination, Registry a, Registry b, bool set_flags); + + /// Encode "SBC/SBCS" operation + void put_inst_sbc(Registry destination, Registry a, Registry b, bool set_flags); + + /// Encode "BIC/BICS" operation + void put_inst_bic(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t lsl6, bool set_flags); + + /// Encode "CLS/CLZ" operation + void put_inst_count(Registry destination, Registry source, uint8_t imm1); + + /// Encode "ILDR/LDRI/LDR" as well as the "ISTR/STRI/STR" operations + void put_inst_ldst(Registry dst, Registry base, int64_t offset, Sizing sizing, MemoryOperation op, MemoryDirection dir); + + /// Encode "SMADDL/UMADDL/SMSUBL/UMSUBL" operation + void put_inst_mulopl(Registry dst, Registry a, Registry b, Registry addend, bool is_unsigned, bool is_subtract); + + /// Encode "UMULH/SMULH" operation + void put_inst_mulh(Registry dst, Registry a, Registry b, bool is_unsigned); + + /// Encode "UDIV/UDIV" operation + void put_inst_div(Registry dst, Registry a, Registry b, bool is_unsigned); + + /// Encode "REV16/REV32/REV64" operation + void put_inst_rev(Registry dst, Registry src, uint16_t size_opc_10); + + /// Encode "LSL/LSR/ROR" operation + void put_inst_shift_v(Registry dst, Registry src, Registry bits, ShiftType shift); + + /// Encode "CSINC/CSEL/CSET/CINC" operation + void put_inst_csinc(Condition condition, Registry dst, Registry truthy, Registry falsy, bool increment, bool invert); + + /// Encode "CAS/CAB/CAH" operations + void put_inst_cas(Registry dst, Registry src, Registry cmp, Order order, uint8_t size); + + /// Encode "LDAR/LDARH/LDARB" operations + void put_inst_ldar(Registry dst, Registry src, uint8_t size); + + /// Encode "LDADD/LDADDH/LDADD" operation + void put_inst_ldop(Registry val, Registry dst, Registry src, Order order, uint8_t size, uint32_t opc); + + /// Encode "LDP/LDPSW" operations + void put_inst_ldpx(Registry r1, Registry r2, Registry src, int64_t offset, MemoryOperation op, uint32_t size, bool load, uint32_t opc); + + /// Encode "ADD/ADDS/SUB/SUBS (immediate)" operation + void put_inst_add_imm(Registry destination, Registry source, uint16_t imm12, bool lsl_12, bool set_flags, bool subtract); + + /// Encode "ADD/ADDS/SUB/SUBS (shifted register)" operation + void put_inst_add_shifted(Registry destination, Registry a, Registry b, ShiftType shift, uint8_t imm6, bool set_flags, bool subtract); + + /// Encode exclusive load/store operations + void put_inst_ldstx(Registry r1, Registry r2, Registry status, Registry mem, uint8_t size, bool load, bool barrier, bool pair); + + /// Encode load/store operations with base-destination registers and 9 bit signed offsets + void put_inst_ldst_simm9(Registry dst, Registry src, int16_t offset, uint32_t size, bool load, uint32_t opc); + + public: + + BufferWriter(SegmentedBuffer& buffer); + + // basic + INST put_adc(Registry dst, Registry a, Registry b); ///< Add with carry + INST put_adcs(Registry dst, Registry a, Registry b); ///< Add with carry and set flags + INST put_add(Registry dst, Registry a, Registry b, Sizing size = Sizing::UX, uint8_t lsl3 = 0); ///< Add two registers, potentially extending one of them + INST put_adds(Registry dst, Registry a, Registry b, Sizing size = Sizing::UX, uint8_t lsl3 = 0); ///< Add two registers, set the flags, potentially extending one of them + INST put_add(Registry dst, Registry src, uint16_t imm12, bool shift_12 = false); ///< Add 12bit, unsigned immediate (optionally shifted by 12 bits) to a register + INST put_adds(Registry dst, Registry src, uint16_t imm12, bool shift_12 = false); ///< Add with Carry 12bit, unsigned immediate (optionally shifted by 12 bits) to a register + INST put_add(Registry destination, Registry a, Registry b, ShiftType shift, uint8_t imm6); ///< Add an optionally-shifted register + INST put_adds(Registry destination, Registry a, Registry b, ShiftType shift, uint8_t imm6); ///< Add, with Carry, an optionally-shifted register + INST put_adr(Registry destination, Label label); ///< Form a PC-relative address + INST put_adrp(Registry destination, Label label); ///< Form a PC-page-relative address + INST put_movz(Registry dst, uint16_t imm, uint16_t shift = 0); ///< Move shifted WORD into register, zero other bits + INST put_movk(Registry dst, uint16_t imm, uint16_t shift = 0); ///< Move shifted WORD into register, keep other bits + INST put_movn(Registry dst, uint16_t imm, uint16_t shift = 0); ///< Move shifted WORD into register, zero other bits, then NOT the register + INST put_mov(Registry dst, uint64_t imm); ///< Move immediate into register + INST put_mov(Registry dst, Registry src); ///< Move value between registers + INST put_movn(Registry dst, Registry src); ///< Move inverted value between registers + INST put_mov(Registry dst, BitPattern pattern); ///< Move BitPattern encoded immediate to a register + INST put_neg(Registry dst, Registry src, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Negate + INST put_negs(Registry dst, Registry src, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Negate, setting flags + INST put_ngc(Registry dst, Registry src); ///< Negate with carry + INST put_ngcs(Registry dst, Registry src); ///< Negate with carry, setting flags + INST put_ret(); ///< Return from procedure using link register + INST put_eret(); ///< Exception return + INST put_ret(Registry src); ///< Return from procedure + INST put_rbit(Registry dst, Registry src); ///< Reverse bits + INST put_clz(Registry dst, Registry src); ///< Count leading zeros + INST put_cls(Registry dst, Registry src); ///< Count leading signs (ones) + INST put_ldr(Registry registry, Label label); ///< Load value from memory + INST put_ildr(Registry dst, Registry base, int64_t offset, Sizing size); ///< Increment base and load value from memory + INST put_ldri(Registry dst, Registry base, int64_t offset, Sizing size); ///< Load value from memory and increment base + INST put_ldr(Registry registry, Registry base, uint64_t offset, Sizing size); ///< Load value from memory + INST put_istr(Registry dst, Registry base, int64_t offset, Sizing size); ///< Increment base and store value to memory + INST put_stri(Registry dst, Registry base, int64_t offset, Sizing size); ///< Store value to memory and increment base + INST put_str(Registry registry, Registry base, uint64_t offset, Sizing size); ///< Store value to memory + INST put_ands(Registry dst, Registry src, BitPattern pattern); ///< Bitwise AND between register and bit pattern + INST put_and(Registry dst, Registry src, BitPattern pattern); ///< Bitwise AND between register and bit pattern, set flags + INST put_ands(Registry dst, Registry a, Registry b, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Bitwise AND between two register, shifting the second one, set flags + INST put_and(Registry dst, Registry a, Registry b, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Bitwise AND between two register, shifting the second one + INST put_eor(Registry destination, Registry source, BitPattern pattern); ///< Bitwise XOR between register and bit pattern + INST put_eor(Registry dst, Registry a, Registry b, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Bitwise XOR between two register, shifting the second one + INST put_eon(Registry dst, Registry a, Registry b, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Bitwise XOR NOT between two register, shifting the second one + INST put_orr(Registry destination, Registry source, BitPattern pattern); ///< Bitwise OR between register and bit pattern + INST put_orr(Registry dst, Registry a, Registry b, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Bitwise OR between two register, shifting the second one + INST put_orn(Registry dst, Registry a, Registry b, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Bitwise OR NOT between two register, shifting the second one + INST put_sbc(Registry dst, Registry a, Registry b); ///< Subtract with Carry + INST put_sbcs(Registry dst, Registry a, Registry b); ///< Subtract with Carry and set flags + INST put_sub(Registry dst, Registry a, Registry b, Sizing size = Sizing::UX, uint8_t lsl3 = 0); ///< Add two registers, potentially extending one of them + INST put_subs(Registry dst, Registry a, Registry b, Sizing size = Sizing::UX, uint8_t lsl3 = 0); ///< Add two registers, set the flags, potentially extending one of them + INST put_sub(Registry dst, Registry src, uint16_t imm12, bool shift_12 = false); ///< Subtract 12bit, unsigned immediate (optionally shifted by 12 bits) to a register + INST put_subs(Registry dst, Registry src, uint16_t imm12, bool shift_12 = false); ///< Subtract with Carry 12bit, unsigned immediate (optionally shifted by 12 bits) to a register + INST put_sub(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t imm6); ///< Add an optionally-shifted register + INST put_subs(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t imm6); ///< Add, with Carry, an optionally-shifted register + INST put_cmp(Registry a, Registry b, Sizing size = Sizing::UX, uint8_t lsl3 = 0); ///< Compare + INST put_cmn(Registry a, Registry b, Sizing size = Sizing::UX, uint8_t lsl3 = 0); ///< Compare negative + INST put_madd(Registry dst, Registry a, Registry b, Registry addend); ///< Multiply and Add 64 bit registers + INST put_smaddl(Registry dst, Registry a, Registry b, Registry addend); ///< Signed multiply two 32 bit registers and add 64 bit register + INST put_umaddl(Registry dst, Registry a, Registry b, Registry addend); ///< Unsigned multiply two 32 bit registers and add 64 bit register + INST put_smsubl(Registry dst, Registry a, Registry b, Registry addend); ///< Signed multiply two 32 bit registers and subtract 64 bit register + INST put_umsubl(Registry dst, Registry a, Registry b, Registry addend); ///< Unsigned multiply two 32 bit registers and subtract 64 bit register + INST put_smnegl(Registry dst, Registry a, Registry b); ///< Signed multiply two 32 bit registers and negate result + INST put_umnegl(Registry dst, Registry a, Registry b); ///< Unsigned multiply two 32 bit registers and negate result + INST put_mul(Registry dst, Registry a, Registry b); ///< Multiply 64 bit registers + INST put_smul(Registry dst, Registry a, Registry b); ///< Signed multiply 32 bit registers + INST put_umul(Registry dst, Registry a, Registry b); ///< Unsigned multiply 32 bit registers + INST put_smulh(Registry dst, Registry a, Registry b); ///< Signed multiply high + INST put_umulh(Registry dst, Registry a, Registry b); ///< Unsigned multiply high + INST put_sdiv(Registry dst, Registry a, Registry b); ///< Signed Divide + INST put_udiv(Registry dst, Registry a, Registry b); ///< Unsigned Divide + INST put_rev16(Registry dst, Registry src); ///< Reverse bytes in 16-bit words + INST put_rev32(Registry dst, Registry src); ///< Reverse bytes in 32-bit dwords + INST put_rev64(Registry dst, Registry src); ///< Reverse bytes in 64-bit qwords + INST put_ror(Registry dst, Registry src, Registry bits); ///< Rotate Right by register + INST put_lsr(Registry dst, Registry src, Registry bits); ///< Logical Shift Right by register + INST put_lsr(Registry dst, Registry src, uint16_t bits); ///< Logical Shift Right by immediate + INST put_lsl(Registry dst, Registry src, Registry bits); ///< Logical Shift Left by register + INST put_lsl(Registry dst, Registry src, uint16_t bits); ///< Logical Shift Left by immediate + INST put_asr(Registry dst, Registry src, Registry bits); ///< Arithmetic Shift Right by register + INST put_asr(Registry dst, Registry src, uint16_t bits); ///< Arithmetic Shift Right by immediate + INST put_asl(Registry dst, Registry src, Registry bits); ///< Arithmetic Shift Left by register + INST put_asl(Registry dst, Registry src, uint16_t bits); ///< Arithmetic Shift Left by immediate + INST put_ror(Registry dst, Registry src, uint8_t imm); ///< Rotate Right by immediate + INST put_extr(Registry dst, Registry left, Registry right, uint8_t imm5); ///< Extract register + INST put_csel(Condition condition, Registry dst, Registry truthy, Registry falsy); ///< Conditional Select + INST put_csinc(Condition condition, Registry dst, Registry truthy, Registry falsy); ///< Conditional Select and Increment if false + INST put_cinc(Condition condition, Registry dst, Registry src);///< Conditional Increment if true + INST put_cinc(Condition condition, Registry dst); ///< Conditional Increment if true + INST put_cset(Condition condition, Registry dst); ///< Conditional Set if true + INST put_tst(Registry reg, BitPattern pattern); ///< Test bits (immediate) + INST put_tst(Registry a, Registry b, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Test shifted register + INST put_sbfm(Registry dst, Registry src, BitPattern pattern); ///< Signed Bitfield Insert in Zero + INST put_ubfm(Registry dst, Registry src, BitPattern pattern); ///< Unsigned Bitfield Insert in Zero + INST put_uxtb(Registry dst, Registry src); ///< Extract Byte + INST put_uxth(Registry dst, Registry src); ///< Extract Two Bytes + INST put_bfm(Registry dst, Registry src, BitPattern pattern); ///< Bitfield Move + INST put_bfc(Registry dst, BitPattern pattern); ///< Bitfield Clear + INST put_bic(Registry dst, Registry a, Registry b, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Bitwise Bit Clear + INST put_bics(Registry dst, Registry a, Registry b, ShiftType shift = ShiftType::LSL, uint8_t lsl6 = 0); ///< Bitwise Bit Clear and set flags + INST put_ldarb(Registry dst, Registry src); ///< Load-Acquire Register byte from memory + INST put_ldarh(Registry dst, Registry src); ///< Load-Acquire Register word from memory + INST put_ldar(Registry dst, Registry src); ///< Load-Acquire Register dword or qword from memory + INST put_ldp(Registry r1, Registry r2, Registry src, int64_t offset = 0); ///< Load a Pair of Registers + INST put_ildp(Registry r1, Registry r2, Registry src, int64_t offset); ///< Increment src and load value pair from memory + INST put_ldpi(Registry r1, Registry r2, Registry src, int64_t offset); ///< Load value pair from memory and increment src + INST put_ldpsw(Registry r1, Registry r2, Registry src, int64_t offset = 0); ///< Load and sign-extend a pair of dwords into two Registers + INST put_ildpsw(Registry r1, Registry r2, Registry src, int64_t offset); ///< Increment src and load sign-extended dword pair from memory + INST put_ldpswi(Registry r1, Registry r2, Registry src, int64_t offset); ///< Load sign-extended dword pair from memory and increment src + INST put_ldnp(Registry r1, Registry r2, Registry src, int64_t offset = 0); ///< Load a non-temporal Pair of Registers + INST put_stp(Registry r1, Registry r2, Registry src, int64_t offset = 0); ///< Store a Pair of Registers + INST put_istp(Registry r1, Registry r2, Registry src, int64_t offset); ///< Increment src and store value pair to memory + INST put_stpi(Registry r1, Registry r2, Registry src, int64_t offset); ///< Store value pair to memory and increment src + INST put_stnp(Registry r1, Registry r2, Registry src, int64_t offset = 0); ///< Store a non-temporal Pair of Registers + INST put_ccmp(Condition condition, Condition flags, Registry reg, uint8_t imm5); ///< Conditional compare + INST put_ccmp(Condition condition, Condition flags, Registry reg, Registry val); ///< Conditional compare + INST put_ccmn(Condition condition, Condition flags, Registry reg, uint8_t imm5); ///< Conditional compare negative + INST put_ccmn(Condition condition, Condition flags, Registry reg, Registry val); ///< Conditional compare negative + INST put_csinv(Condition condition, Registry dst, Registry truthy, Registry falsy); ///< Conditional select truthy or invert falsy + INST put_cinv(Condition condition, Registry dst, Registry src); ///< Conditional select or invert + INST put_csetm(Condition condition, Registry dst); ///< Conditional set mask + INST put_csneg(Condition condition, Registry dst, Registry truthy, Registry falsy); ///< Conditional select truthy or negate falsy + INST put_cneg(Condition condition, Registry dst, Registry src); ///< Conditional select or negate + INST put_cmn(Registry src, uint16_t imm12, bool shift_12 = false); ///< Compare negative with immediate + INST put_cmn(Registry a, Registry b, ShiftType shift, uint8_t imm6); ///< Compare negative with shifted register + INST put_cmp(Registry src, uint16_t imm12, bool shift_12 = false); ///< Compare with immediate + INST put_cmp(Registry a, Registry b, ShiftType shift, uint8_t imm6); ///< Compare with shifted register + INST put_ldxp(Registry r1, Registry r2, Registry src); ///< Load exclusive pair of registers + INST put_ldaxp(Registry r1, Registry r2, Registry src); ///< Load-acquire exclusive pair of registers + INST put_stxp(Registry status, Registry r1, Registry r2, Registry src); ///< Store exclusive pair of registers + INST put_stlxp(Registry status, Registry r1, Registry r2, Registry src); ///< Store-release exclusive pair of registers + INST put_ldaxr(Registry dst, Registry src); ///< Load-acquire dword/qword exclusive register + INST put_ldaxrh(Registry dst, Registry src); ///< Load-acquire word exclusive register + INST put_ldaxrb(Registry dst, Registry src); ///< Load-acquire byte exclusive register + INST put_ldxr(Registry dst, Registry src); ///< Load dword/qword exclusive register + INST put_ldxrh(Registry dst, Registry src); ///< Load word exclusive register + INST put_ldxrb(Registry dst, Registry src); ///< Load byte exclusive register + INST put_stlxr(Registry status, Registry dst, Registry src); ///< Store-release dword/qword exclusive register + INST put_stlxrh(Registry status, Registry dst, Registry src); ///< Store-release word exclusive register + INST put_stlxrb(Registry status, Registry dst, Registry src); ///< Store-release byte exclusive register + INST put_stxr(Registry status, Registry dst, Registry src); ///< Store dword/qword exclusive register + INST put_stxrh(Registry status, Registry dst, Registry src); ///< Store word exclusive register + INST put_stxrb(Registry status, Registry dst, Registry src); ///< Store byte exclusive register + INST put_ldtr(Registry dst, Registry src, int16_t offset); ///< Load dword/qword register (unprivileged) at unscaled offset + INST put_ldtrh(Registry dst, Registry src, int16_t offset); ///< Load word register (unprivileged) at unscaled offset + INST put_ldtrb(Registry dst, Registry src, int16_t offset); ///< Load byte register (unprivileged) at unscaled offset + INST put_sttr(Registry dst, Registry src, int16_t offset); ///< Store dword/qword register (unprivileged) at unscaled offset + INST put_sttrh(Registry dst, Registry src, int16_t offset); ///< Store word register (unprivileged) at unscaled offset + INST put_sttrb(Registry dst, Registry src, int16_t offset); ///< Store byte register (unprivileged) at unscaled offset + INST put_ldur(Registry dst, Registry src, int16_t offset); ///< Load dword/qword register at unscaled offset + INST put_ldurh(Registry dst, Registry src, int16_t offset); ///< Load word register at unscaled offset + INST put_ldurb(Registry dst, Registry src, int16_t offset); ///< Load byte register at unscaled offset + INST put_stur(Registry dst, Registry src, int16_t offset); ///< Store dword/qword register at unscaled offset + INST put_sturh(Registry dst, Registry src, int16_t offset); ///< Store word register at unscaled offset + INST put_sturb(Registry dst, Registry src, int16_t offset); ///< Store byte register at unscaled offset + INST put_sxtw(Registry dst, Registry src); ///< Sign extend dword + INST put_sxth(Registry dst, Registry src); ///< Sign extend word + INST put_sxtb(Registry dst, Registry src); ///< Sign extend byte + INST put_prfm(Prefetch mode, Registry base, Registry index); ///< Prefetch memory + + // large system extension + INST put_swpb(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Swap byte from 'src' to memory + INST put_swph(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Swap word from 'src' to memory + INST put_swp(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Swap dword or qword from 'src' to memory + INST put_casb(Registry ptr, Registry src, Registry cmp, Order order = Order::NONE); ///< Compare and Swap byte in memory + INST put_cash(Registry ptr, Registry src, Registry cmp, Order order = Order::NONE); ///< Compare and Swap word in memory + INST put_cas(Registry ptr, Registry src, Registry cmp, Order order = Order::NONE); ///< Compare and Swap dword or qword in memory + INST put_ldaddb(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic add on byte + INST put_ldaddh(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic add on word + INST put_ldadd(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic add on dword or qword + INST put_ldclrb(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic bit clear on byte + INST put_ldclrh(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic bit clear on word + INST put_ldclr(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic bit clear on dword or qword + INST put_ldeorb(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic bitwise xor on byte + INST put_ldeorh(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic bitwise xor on word + INST put_ldeor(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic bitwise xor on dword or qword + INST put_ldsetb(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic bit set on byte + INST put_ldseth(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic bit set on word + INST put_ldset(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic bit set on dword or qword + INST put_ldsmaxb(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic signed maximum on byte + INST put_ldsmaxh(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic signed maximum on word + INST put_ldsmax(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic signed maximum on dword or qword + INST put_ldumaxb(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic signed maximum on byte + INST put_ldumaxh(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic signed maximum on word + INST put_ldumax(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic signed maximum on dword or qword + INST put_ldsminb(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic signed maximum on byte + INST put_ldsminh(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic signed maximum on word + INST put_ldsmin(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic signed maximum on dword or qword + INST put_lduminb(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic signed maximum on byte + INST put_lduminh(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic signed maximum on word + INST put_ldumin(Registry val, Registry dst, Registry src, Order order = Order::NONE); ///< Atomic signed maximum on dword or qword + + // large system extension aliases + INST put_staddb(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic add on byte + INST put_staddh(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic add on word + INST put_stadd(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic add on dword or qword + INST put_stclrb(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic bit clear on byte + INST put_stclrh(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic bit clear on word + INST put_stclr(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic bit clear on dword or qword + INST put_steorb(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic bitwise xor on byte + INST put_steorh(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic bitwise xor on word + INST put_steor(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic bitwise xor on dword or qword + INST put_stsetb(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic bit set on byte + INST put_stseth(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic bit set on word + INST put_stset(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic bit set on dword or qword + INST put_stsmaxb(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic signed maximum on byte + INST put_stsmaxh(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic signed maximum on word + INST put_stsmax(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic signed maximum on dword or qword + INST put_stumaxb(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic signed maximum on byte + INST put_stumaxh(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic signed maximum on word + INST put_stumax(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic signed maximum on dword or qword + INST put_stsminb(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic signed maximum on byte + INST put_stsminh(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic signed maximum on word + INST put_stsmin(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic signed maximum on dword or qword + INST put_stuminb(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic signed maximum on byte + INST put_stuminh(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic signed maximum on word + INST put_stumin(Registry val, Registry ptr, Order order = Order::NONE); ///< Atomic signed maximum on dword or qword + + // control + INST put_clrex(uint8_t imm4 = 15); ///< This instruction clears the local monitor of the executing PE. + INST put_svc(uint16_t imm16); ///< Supervisor call + INST put_hvc(uint16_t imm16); ///< Hypervisor Call + INST put_smc(uint16_t imm16); ///< Secure Monitor Call + INST put_hlt(uint16_t imm16); ///< Halt + INST put_brk(uint16_t imm16); ///< Breakpoint Instruction exception + INST put_hint(uint8_t imm7); ///< Architectural hint + INST put_isb(); ///< Instruction Synchronization Barrier + INST put_nop(); ///< No operation + INST put_yield(); ///< Indicate spin-lock + INST put_wfe(); ///< Wait For Event + INST put_wfi(); ///< Wait For Interrupt + INST put_sev(); ///< Send Event + INST put_sevl(); ///< Send Event Local + INST put_esb(); ///< Error Synchronization Barrier + INST put_psb(); ///< Profiling Synchronization Barrier. + + // branch + INST put_b(const Label& label); ///< Branch + INST put_b(Condition condition, const Label& label); ///< Branch conditionally + INST put_bl(const Label& label); ///< Branch with link + INST put_blr(Registry ptr); ///< Branch with link to register + INST put_br(Registry ptr); ///< Branch to register + INST put_cbnz(Registry src, const Label& label); ///< Branch if register is not zero + INST put_cbz(Registry src, const Label& label); ///< Branch if register is zero + INST put_tbz(Registry test, uint16_t bit6, const Label& label); ///< Test bit and Branch if Zero + INST put_tbnz(Registry test, uint16_t bit6, const Label& label); ///< Test bit and Branch if Not Zero + + }; + +} diff --git a/src/asmio/aarch64/writer_basic.cpp b/src/asmio/aarch64/writer_basic.cpp new file mode 100644 index 0000000..aae7494 --- /dev/null +++ b/src/asmio/aarch64/writer_basic.cpp @@ -0,0 +1,1239 @@ +#include "writer.hpp" +#include + +namespace asmio::arm { + + /* + * class BufferWriter + */ + + void BufferWriter::put_adc(Registry dst, Registry a, Registry b) { + put_inst_adc(dst, a, b, false); + } + + void BufferWriter::put_adcs(Registry dst, Registry a, Registry b) { + put_inst_adc(dst, a, b, true); + } + + void BufferWriter::put_add(Registry dst, Registry a, Registry b, Sizing size, uint8_t lsl3) { + put_inst_extended_register(0b0'0'01011001, dst, a, b, size, lsl3, false); + } + + void BufferWriter::put_adds(Registry dst, Registry a, Registry b, Sizing size, uint8_t lsl3) { + put_inst_extended_register(0b0'0'01011001, dst, a, b, size, lsl3, true); + } + + void BufferWriter::put_add(Registry dst, Registry src, uint16_t imm12, bool shift12) { + put_inst_add_imm(dst, src, imm12, shift12, false, false); + } + + void BufferWriter::put_adds(Registry dst, Registry src, uint16_t imm12, bool shift12) { + put_inst_add_imm(dst, src, imm12, shift12, true, false); + } + + void BufferWriter::put_add(Registry destination, Registry a, Registry b, ShiftType shift, uint8_t imm6) { + put_inst_add_shifted(destination, a, b, shift, imm6, false, false); + } + + void BufferWriter::put_adds(Registry destination, Registry a, Registry b, ShiftType shift, uint8_t imm6) { + put_inst_add_shifted(destination, a, b, shift, imm6, true, false); + } + + void BufferWriter::put_adr(Registry destination, Label label) { + buffer.add_linkage(label, LinkageType::AARCH64_21_5_LO_HI); + put_dword(0b0 << 31 | 0b10000 << 24 | destination.reg); + } + + void BufferWriter::put_adrp(Registry destination, Label label) { + buffer.add_linkage(label, LinkageType::AARCH64_21_5_LO_HI); + put_dword(0b1 << 31 | 0b10000 << 24 | destination.reg); + } + + void BufferWriter::put_movz(Registry registry, uint16_t imm, uint16_t shift) { + put_inst_mov(registry, 0b10100101, imm, shift); + } + + void BufferWriter::put_movk(Registry registry, uint16_t imm, uint16_t shift) { + put_inst_mov(registry, 0b11100101, imm, shift); + } + + void BufferWriter::put_movn(Registry registry, uint16_t imm, uint16_t shift) { + put_inst_mov(registry, 0b00100101, imm, shift); + } + + void BufferWriter::put_mov(Registry dst, uint64_t imm) { + + if (dst.is(Registry::ZERO)) { + return; // do nothing + } + + if (imm <= UINT16_MAX) { + put_movz(dst, imm); + return; + } + + const uint64_t inv = ~imm; + + if (inv <= UINT16_MAX) { + put_movn(dst, inv); + return; + } + + const auto nrs = BitPattern::try_pack(imm); + + if (nrs.ok()) { + return put_mov(dst, nrs); + } + + const size_t length = dst.wide() ? 64 : 32; + + // TODO this can be made better by using movn/movz strategically here + put_movz(dst, imm & UINT16_MAX); + + for (size_t i = 16; i < length; i += 16) { + imm >>= 16; + uint16_t part = imm & UINT16_MAX; + + if (part) { + put_movk(dst, part, i); + } + } + + } + + void BufferWriter::put_mov(Registry dst, Registry src) { + + if (src.is(Registry::STACK) || dst.is(Registry::STACK)) { + + // when dealing with SP zero can't be used + if (src.is(Registry::ZERO) || dst.is(Registry::ZERO)) { + throw std::runtime_error {"Invalid operands, zero registry can't be used in this context"}; + } + + put_add(dst, src, XZR); + return; + } + + put_orr(dst, src, dst.wide() ? XZR : WZR); + } + + void BufferWriter::put_movn(Registry dst, Registry src) { + put_orn(dst, dst.wide() ? XZR : WZR, src); + } + + void BufferWriter::put_mov(Registry dst, BitPattern pattern) { + put_orr(dst, dst.wide() ? XZR : WZR, pattern); + } + + void BufferWriter::put_neg(Registry dst, Registry src, ShiftType shift, uint8_t imm6) { + put_sub(dst, dst.wide() ? XZR : WZR, src, shift, imm6); + } + + void BufferWriter::put_negs(Registry dst, Registry src, ShiftType shift, uint8_t imm6) { + put_subs(dst, dst.wide() ? XZR : WZR, src, shift, imm6); + } + + void BufferWriter::put_ngc(Registry dst, Registry src) { + put_sbc(dst, dst.wide() ? XZR : WZR, src); + } + + void BufferWriter::put_ngcs(Registry dst, Registry src) { + put_sbcs(dst, dst.wide() ? XZR : WZR, src); + } + + void BufferWriter::put_ret() { + put_ret(LR); + } + + void BufferWriter::put_eret() { + put_dword(0b110'101'1'0100'11111'0000'0'0'11111'00000); + } + + void BufferWriter::put_ret(Registry registry) { + + if (!registry.wide()) { + throw std::runtime_error {"Invalid operand, non-qword register can't be used here"}; + } + + if (!registry.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid operand, expected general purpose register"}; + } + + put_dword(0b1101011001011111000000'00000'00000 | registry.reg << 5); + } + + void BufferWriter::put_rbit(Registry dst, Registry src) { + + if (dst.wide() != src.wide()) { + throw std::runtime_error {"Invalid operands, both registers need to be of the same size"}; + } + + uint16_t sf = dst.wide() ? 1 : 0; + put_dword(sf << 31 | 0b1011010110 << 21 | src.reg << 5 | dst.reg); + } + + void BufferWriter::put_clz(Registry dst, Registry src) { + put_inst_count(dst, src, 0); + } + + void BufferWriter::put_cls(Registry dst, Registry src) { + put_inst_count(dst, src, 1); + } + + void BufferWriter::put_ldr(Registry registry, Label label) { + uint16_t sf = registry.wide() ? 1 : 0; + buffer.add_linkage(label, LinkageType::AARCH64_19_5_ALIGNED); + put_dword(sf << 30 | 0b011000 << 24 | registry.reg); + } + + void BufferWriter::put_ldri(Registry dst, Registry base, int64_t offset, Sizing sizing) { + put_inst_ldst(dst, base, offset, sizing, POST, LOAD); + } + + void BufferWriter::put_ildr(Registry dst, Registry base, int64_t offset, Sizing sizing) { + put_inst_ldst(dst, base, offset, sizing, PRE, LOAD); + } + + void BufferWriter::put_ldr(Registry dst, Registry base, uint64_t offset, Sizing sizing) { + put_inst_ldst(dst, base, std::bit_cast(offset), sizing, OFFSET, LOAD); + } + + void BufferWriter::put_stri(Registry dst, Registry base, int64_t offset, Sizing sizing) { + put_inst_ldst(dst, base, offset, sizing, POST, STORE); + } + + void BufferWriter::put_istr(Registry dst, Registry base, int64_t offset, Sizing sizing) { + put_inst_ldst(dst, base, offset, sizing, PRE, STORE); + } + + void BufferWriter::put_str(Registry dst, Registry base, uint64_t offset, Sizing sizing) { + put_inst_ldst(dst, base, std::bit_cast(offset), sizing, OFFSET, STORE); + } + + void BufferWriter::put_ands(Registry dst, Registry src, BitPattern pattern) { + + if (!src.is(Registry::GENERAL) || !dst.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid operand, expected general purpose register"}; + } + + put_inst_bitmask_immediate(0b11'100100, dst, src, pattern); + } + + void BufferWriter::put_and(Registry dst, Registry src, BitPattern pattern) { + + if (!src.is(Registry::GENERAL) || !dst.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid operand, expected general purpose register"}; + } + + put_inst_bitmask_immediate(0b11'100100, dst, src, pattern); + } + + void BufferWriter::put_ands(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t imm6) { + put_inst_shifted_register(0b1101010, 0, dst, a, b, imm6, shift); + } + + void BufferWriter::put_and(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t imm6) { + put_inst_shifted_register(0b0001010, 0, dst, a, b, imm6, shift); + } + + void BufferWriter::put_eor(Registry dst, Registry src, BitPattern pattern) { + + if (!src.is(Registry::GENERAL) || !dst.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid operand, expected general purpose register"}; + } + + put_inst_bitmask_immediate(0b10'100100, dst, src, pattern); + } + + void BufferWriter::put_eor(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t imm6) { + put_inst_shifted_register(0b1001010, 0, dst, a, b, imm6, shift); + } + + void BufferWriter::put_eon(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t imm6) { + put_inst_shifted_register(0b1001010, 1, dst, a, b, imm6, shift); + } + + void BufferWriter::put_orr(Registry destination, Registry source, BitPattern pattern) { + + // destination can be SP + if (!source.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid operand, expected source to be a general purpose register"}; + } + + put_inst_bitmask_immediate(0b01100100, destination, source, pattern); + } + + void BufferWriter::put_orr(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t imm6) { + put_inst_shifted_register(0b0101010, 0, dst, a, b, imm6, shift); + } + + void BufferWriter::put_orn(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t imm6) { + put_inst_shifted_register(0b0101010, 1, dst, a, b, imm6, shift); + } + + void BufferWriter::put_clrex(uint8_t imm4) { + put_dword(0b110'101'01000000110011 << 12 | (imm4 & 0b1111) << 8 | 0b010'11111); + } + + void BufferWriter::put_svc(uint16_t imm16) { + put_dword(0b11010100000 << 21 | imm16 << 5 | 0b00001); + } + + void BufferWriter::put_sbc(Registry dst, Registry a, Registry b) { + put_inst_sbc(dst, a, b, false); + } + + void BufferWriter::put_sbcs(Registry dst, Registry a, Registry b) { + put_inst_sbc(dst, a, b, true); + } + + void BufferWriter::put_sub(Registry dst, Registry a, Registry b, Sizing size, uint8_t lsl3) { + put_inst_extended_register(0b1'0'01011001, dst, a, b, size, lsl3, false); + } + + void BufferWriter::put_subs(Registry dst, Registry a, Registry b, Sizing size, uint8_t lsl3) { + put_inst_extended_register(0b1'0'01011001, dst, a, b, size, lsl3, true); + } + + void BufferWriter::put_sub(Registry dst, Registry src, uint16_t imm12, bool shift_12) { + put_inst_add_imm(dst, src, imm12, shift_12, false, true); + } + + void BufferWriter::put_subs(Registry dst, Registry src, uint16_t imm12, bool shift_12) { + put_inst_add_imm(dst, src, imm12, shift_12, true, true); + } + + void BufferWriter::put_sub(Registry destination, Registry a, Registry b, ShiftType shift, uint8_t imm6) { + put_inst_add_shifted(destination, a, b, shift, imm6, false, true); + } + + void BufferWriter::put_subs(Registry destination, Registry a, Registry b, ShiftType shift, uint8_t imm6) { + put_inst_add_shifted(destination, a, b, shift, imm6, true, true); + } + + void BufferWriter::put_cmp(Registry a, Registry b, Sizing size, uint8_t lsl3) { + put_subs(a.wide() ? XZR : WZR, a, b, size, lsl3); + } + + void BufferWriter::put_cmn(Registry a, Registry b, Sizing size, uint8_t lsl3) { + put_adds(a.wide() ? XZR : WZR, a, b, size, lsl3); + } + + void BufferWriter::put_madd(Registry dst, Registry a, Registry b, Registry addend) { + assert_register_triplet(a, b, dst); + + // we have four register so the last one needs to be checked manually + if (dst.wide() != addend.wide()) { + throw std::runtime_error {"Invalid operands, all given registers need to be of the same width"}; + } + + uint32_t sf = dst.wide() ? 1 : 0; + put_dword(sf << 31 | 0b0011011000 << 21 | b.reg << 16 | addend.reg << 10 | a.reg << 5 | dst.reg); + } + + void BufferWriter::put_smaddl(Registry dst, Registry a, Registry b, Registry addend) { + put_inst_mulopl(dst, a, b, addend, false, false); + } + + void BufferWriter::put_umaddl(Registry dst, Registry a, Registry b, Registry addend) { + put_inst_mulopl(dst, a, b, addend, true, false); + } + + void BufferWriter::put_smsubl(Registry dst, Registry a, Registry b, Registry addend) { + put_inst_mulopl(dst, a, b, addend, false, true); + } + + void BufferWriter::put_umsubl(Registry dst, Registry a, Registry b, Registry addend) { + put_inst_mulopl(dst, a, b, addend, true, true); + } + + void BufferWriter::put_smnegl(Registry dst, Registry a, Registry b) { + put_smsubl(dst, a, b, XZR); + } + + void BufferWriter::put_umnegl(Registry dst, Registry a, Registry b) { + put_umsubl(dst, a, b, XZR); + } + + void BufferWriter::put_mul(Registry dst, Registry a, Registry b) { + put_madd(dst, a, b, dst.wide() ? XZR : WZR); + } + + void BufferWriter::put_smul(Registry dst, Registry a, Registry b) { + put_smaddl(dst, a, b, XZR); + } + + void BufferWriter::put_umul(Registry dst, Registry a, Registry b) { + put_umaddl(dst, a, b, XZR); + } + + void BufferWriter::put_smulh(Registry dst, Registry a, Registry b) { + put_inst_mulh(dst, a, b, false); + } + + void BufferWriter::put_umulh(Registry dst, Registry a, Registry b) { + put_inst_mulh(dst, a, b, true); + } + + void BufferWriter::put_sdiv(Registry dst, Registry a, Registry b) { + put_inst_div(dst, a, b, false); + } + + void BufferWriter::put_udiv(Registry dst, Registry a, Registry b) { + put_inst_div(dst, a, b, true); + } + + void BufferWriter::put_rev16(Registry dst, Registry src) { + put_inst_rev(dst, src, 0b01); + } + + void BufferWriter::put_rev32(Registry dst, Registry src) { + put_inst_rev(dst, src, 0b10); + } + + void BufferWriter::put_rev64(Registry dst, Registry src) { + put_inst_rev(dst, src, 0b11); + } + + void BufferWriter::put_ror(Registry dst, Registry src, Registry bits) { + put_inst_shift_v(dst, src, bits, ShiftType::ROR); + } + + void BufferWriter::put_lsr(Registry dst, Registry src, Registry bits) { + put_inst_shift_v(dst, src, bits, ShiftType::LSR); + } + + void BufferWriter::put_lsr(Registry dst, Registry src, uint16_t shift) { + const uint32_t width = dst.size * 8; + const uint32_t ones = width - 1; // one bit gets discarded anyway + + if (shift > ones) { + throw std::runtime_error {"Invalid operand, can't shift by more than register width"}; + } + + // the ISA doesn't mention us needing to that but for shift=0 + // the top bit would be cut of without any shifting to cover that, + // there were similar issues in LSL (immediate). + if (shift == 0) { + put_mov(dst, src); + return; + } + + put_ubfm(dst, src, {width, ones, shift}); + } + + void BufferWriter::put_lsl(Registry dst, Registry src, Registry bits) { + put_inst_shift_v(dst, src, bits, ShiftType::LSL); + } + + void BufferWriter::put_lsl(Registry dst, Registry src, uint16_t shift) { + const uint32_t width = dst.size * 8; + const uint32_t ones = width - 1; // one bit gets discarded anyway + + if (shift > ones) { + throw std::runtime_error {"Invalid operand, can't shift by more than register width"}; + } + + if (shift == 0) { + put_mov(dst, src); + return; + } + + // TODO we do (width - shift) here while the ISA says to use (ones - shift) + // but that causes the top one bit to not be copied, is that a mistake in the + // specification? As with length set to 64 (for shift 0) the BitPattern would be invalid + // we also need to check for shift=0 and encode this using a normal mov. + // The debugger sees this as the intended LSL alias. + put_ubfm(dst, src, {width, width - shift, -shift % width}); + } + + void BufferWriter::put_asr(Registry dst, Registry src, Registry bits) { + put_inst_shift_v(dst, src, bits, ShiftType::ASR); + } + + void BufferWriter::put_asr(Registry dst, Registry src, uint16_t shift) { + const uint32_t width = dst.size * 8; + const uint32_t ones = width - 1; // one bit gets discarded anyway + + if (shift > ones) { + throw std::runtime_error {"Invalid operand, can't shift by more than register width"}; + } + + // the ISA doesn't mention us needing to that but for shift=0 + // the top bit would be cut of without any shifting to cover that, + // there were similar issues in LSL (immediate). + if (shift == 0) { + put_mov(dst, src); + return; + } + + put_sbfm(dst, src, {width, ones, shift}); + } + + void BufferWriter::put_asl(Registry dst, Registry src, Registry bits) { + put_lsl(dst, src, bits); + } + + void BufferWriter::put_asl(Registry dst, Registry src, uint16_t bits) { + put_lsl(dst, src, bits); + } + + void BufferWriter::put_ror(Registry dst, Registry src, uint8_t imm5) { + put_extr(dst, src, src, imm5); + } + + void BufferWriter::put_extr(Registry dst, Registry low, Registry high, uint8_t imm5) { + assert_register_triplet(dst, low, high); + const uint8_t max_shift = dst.wide() ? 63 : 31; + + if (imm5 > max_shift) { + throw std::runtime_error {"Invalid operands, shift value too large for this context"}; + } + + const uint16_t sf = dst.wide() ? 1 : 0; + put_dword(sf << 31 | 0b00100111 << 23 | sf << 22 | low.reg << 16 | imm5 << 10 | high.reg << 5 | dst.reg); + } + + void BufferWriter::put_csel(Condition condition, Registry dst, Registry truthy, Registry falsy) { + put_inst_csinc(condition, dst, truthy, falsy, false, false); + } + + void BufferWriter::put_csinc(Condition condition, Registry dst, Registry truthy, Registry falsy) { + put_inst_csinc(condition, dst, truthy, falsy, true, false); + } + + void BufferWriter::put_cinc(Condition condition, Registry dst, Registry src) { + put_csinc(invert(condition), dst, src, src); + } + + void BufferWriter::put_cinc(Condition condition, Registry dst) { + put_csinc(invert(condition), dst, dst, dst); + } + + void BufferWriter::put_cset(Condition condition, Registry dst) { + put_cinc(condition, dst, dst.wide() ? XZR : WZR); + } + + void BufferWriter::put_tst(Registry reg, BitPattern pattern) { + put_ands(reg.wide() ? XZR : WZR, reg, pattern); + } + + void BufferWriter::put_tst(Registry a, Registry b, ShiftType shift, uint8_t lsl6) { + put_ands(a.wide() ? XZR : WZR, a, b, shift, lsl6); + } + + void BufferWriter::put_sbfm(Registry dst, Registry src, BitPattern pattern) { + + if (!src.is(Registry::GENERAL) || !dst.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid operand, expected general purpose register"}; + } + + put_inst_bitmask_immediate(0b00'100110, dst, src, pattern); + } + + void BufferWriter::put_ubfm(Registry dst, Registry src, BitPattern pattern) { + + if (!src.is(Registry::GENERAL) || !dst.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid operand, expected general purpose register"}; + } + + put_inst_bitmask_immediate(0b10'100110, dst, src, pattern); + } + + void BufferWriter::put_uxtb(Registry dst, Registry src) { + put_ubfm(dst, src, 0xFF); + } + + void BufferWriter::put_uxth(Registry dst, Registry src) { + put_ubfm(dst, src, 0xFFFF); + } + + void BufferWriter::put_bfm(Registry dst, Registry src, BitPattern pattern) { + + if (!src.is(Registry::GENERAL) || !dst.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid operand, expected general purpose register"}; + } + + put_inst_bitmask_immediate(0b01'100110, dst, src, pattern); + } + + void BufferWriter::put_bfc(Registry dst, BitPattern pattern) { + put_bfm(dst, dst.wide() ? XZR : WZR, pattern); + } + + void BufferWriter::put_bic(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t lsl6) { + put_inst_bic(dst, a, b, shift, lsl6, false); + } + + void BufferWriter::put_bics(Registry dst, Registry a, Registry b, ShiftType shift, uint8_t lsl6) { + put_inst_bic(dst, a, b, shift, lsl6, true); + } + + void BufferWriter::put_inst_cas(Registry ptr, Registry src, Registry cmp, Order order, uint8_t size) { + if (!ptr.wide()) { + throw std::runtime_error {"Invalid operand, destination register must be wide"}; + } + + if (ptr.is(Registry::ZERO)) { + throw std::runtime_error {"Invalid operand, destination can't be the zero register"}; + } + + if (!cmp.is(Registry::GENERAL) || !src.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid operand, expected general purpose source and compare register"}; + } + + // "L" flag is used to mark acquire semantics (marked with suffix "A" in mnemonics), + // and o0 to mark "release" (suffix "L" in mnemonics). How designed it like this?? + const uint32_t lf = is_order_acquire(order) ? (1 << 22) : 0; + const uint32_t of = is_order_release(order) ? (1 << 15) : 0; + + put_dword(size << 30 | lf | of | 0b001000'101 << 21 | cmp.reg << 16 | 0b11111 << 10 | ptr.reg << 5 | src.reg); + } + + void BufferWriter::put_swpb(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b00, 0b1000); + } + + void BufferWriter::put_swph(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b01, 0b1000); + } + + void BufferWriter::put_swp(Registry val, Registry dst, Registry src, Order order) { + if (val.wide() != dst.wide()) { + throw std::runtime_error {"Invalid operand, value and destination need to be of the same size"}; + } + + put_inst_ldop(val, dst, src, order, 0b10 | val.wide(), 0b1000); + } + + void BufferWriter::put_casb(Registry dst, Registry src, Registry cmp, Order order) { + put_inst_cas(dst, src, cmp, order, 0b00); + } + + void BufferWriter::put_cash(Registry dst, Registry src, Registry cmp, Order order) { + put_inst_cas(dst, src, cmp, order, 0b01); + } + + void BufferWriter::put_cas(Registry dst, Registry src, Registry cmp, Order order) { + if (src.wide() != cmp.wide()) { + throw std::runtime_error {"Invalid operands, source and compare registers need to be of the same size"}; + } + + put_inst_cas(dst, src, cmp, order, 0b10 | (dst.wide() ? 1 : 0)); + } + + void BufferWriter::put_inst_ldar(Registry dst, Registry src, uint8_t size) { + if (!src.wide()) { + throw std::runtime_error {"Invalid operand, source register must be wide"}; + } + + if (src.is(Registry::ZERO)) { + throw std::runtime_error {"Invalid operand, source can't be the zero register"}; + } + + if (!dst.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid operand, destination must be a general purpose register"}; + } + + put_dword(size << 30 | 0b001000'110'11111'1'11111 << 10 | src.reg << 5 | dst.reg); + } + + void BufferWriter::put_ldarb(Registry dst, Registry src) { + put_inst_ldar(dst, src, 0b00); + } + + void BufferWriter::put_ldarh(Registry dst, Registry src) { + put_inst_ldar(dst, src, 0b01); + } + + void BufferWriter::put_ldar(Registry dst, Registry src) { + put_inst_ldar(dst, src, 0b10 | (dst.wide() ? 1 : 0)); + } + + void BufferWriter::put_inst_ldpx(Registry r1, Registry r2, Registry src, int64_t offset, MemoryOperation op, uint32_t size, bool load, uint32_t opc) { + if (!src.wide()) { + throw std::runtime_error {"Invalid operand, source register must be wide"}; + } + + if (src.is(Registry::ZERO)) { + throw std::runtime_error {"Invalid operand, source can't be the zero register"}; + } + + if (r1.wide() != r2.wide()) { + throw std::runtime_error {"Invalid operands, both destination registers need to be of the same size"}; + } + + if (!r1.is(Registry::GENERAL) || !r2.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid operands, both destination registers need to be general purpose"}; + } + + int32_t imm7 = offset / size; + + if (offset % size) { + throw std::runtime_error {"Invalid operand, immediate value not divisible by operand size"}; + } + + if (imm7 < -64 || imm7 > 63) { + throw std::runtime_error {"Invalid operand, immediate value out of range"}; + } + + // TODO maybe we can find some common bit pattern for this enum? + uint32_t bits = 0; + if (op == POST) bits = 0b01; + else if (op == PRE) bits = 0b11; + else if (op == OFFSET) bits = 0b10; + + put_dword(opc << 30 | 0b101 << 27 | bits << 23 | load << 22 | (imm7 & 0x7f) << 15 | r2.reg << 10 | src.reg << 5 | r1.reg); + } + + void BufferWriter::put_ldp(Registry r1, Registry r2, Registry src, int64_t offset) { + put_inst_ldpx(r1, r2, src, offset, OFFSET, r1.size, true, r1.wide() << 1); + } + + void BufferWriter::put_ildp(Registry r1, Registry r2, Registry src, int64_t offset) { + put_inst_ldpx(r1, r2, src, offset, PRE, r1.size, true, r1.wide() << 1); + } + + void BufferWriter::put_ldpi(Registry r1, Registry r2, Registry src, int64_t offset) { + put_inst_ldpx(r1, r2, src, offset, POST, r1.size, true, r1.wide() << 1); + } + + void BufferWriter::put_ldpsw(Registry r1, Registry r2, Registry src, int64_t offset) { + if (!r1.wide()) { + throw std::runtime_error {"Invalid operand, expected qword destination registers"}; + } + + put_inst_ldpx(r1, r2, src, offset, OFFSET, DWORD, true, 1); + } + + void BufferWriter::put_ildpsw(Registry r1, Registry r2, Registry src, int64_t offset) { + if (!r1.wide()) { + throw std::runtime_error {"Invalid operand, expected qword destination registers"}; + } + + put_inst_ldpx(r1, r2, src, offset, PRE, DWORD, true, 1); + } + + void BufferWriter::put_ldpswi(Registry r1, Registry r2, Registry src, int64_t offset) { + if (!r1.wide()) { + throw std::runtime_error {"Invalid operand, expected qword destination registers"}; + } + + put_inst_ldpx(r1, r2, src, offset, POST, DWORD, true, 1); + } + + void BufferWriter::put_ldnp(Registry r1, Registry r2, Registry src, int64_t offset) { + // FIXME this cast is an ugly hack, we use this to get bits == 0 in put_inst_ldpx() + put_inst_ldpx(r1, r2, src, offset, static_cast(-1), r1.size, true, r1.wide() << 1); + } + + void BufferWriter::put_stp(Registry r1, Registry r2, Registry src, int64_t offset) { + put_inst_ldpx(r1, r2, src, offset, OFFSET, r1.size, false, r1.wide() << 1); + } + + void BufferWriter::put_istp(Registry r1, Registry r2, Registry src, int64_t offset) { + put_inst_ldpx(r1, r2, src, offset, PRE, r1.size, false, r1.wide() << 1); + } + + void BufferWriter::put_stpi(Registry r1, Registry r2, Registry src, int64_t offset) { + put_inst_ldpx(r1, r2, src, offset, POST, r1.size, false, r1.wide() << 1); + } + + void BufferWriter::put_stnp(Registry r1, Registry r2, Registry src, int64_t offset) { + // FIXME this cast is an ugly hack, we use this to get bits == 0 in put_inst_ldpx() + put_inst_ldpx(r1, r2, src, offset, static_cast(-1), r1.size, false, r1.wide() << 1); + } + + void BufferWriter::put_ccmp(Condition condition, Condition flags, Registry val, uint8_t imm5) { + put_dword(val.wide() << 31 | 0b11'11010010 << 21 | (imm5 & 0b11111) << 16 | uint32_t(condition) << 12 | 0b10 << 10 | val.reg << 5 | uint32_t(flags)); + } + + void BufferWriter::put_ccmn(Condition condition, Condition flags, Registry val, uint8_t imm5) { + put_dword(val.wide() << 31 | 0b01'11010010 << 21 | (imm5 & 0b11111) << 16 | uint32_t(condition) << 12 | 0b10 << 10 | val.reg << 5 | uint32_t(flags)); + } + + void BufferWriter::put_ccmp(Condition condition, Condition flags, Registry val, Registry second) { + if (val.size != second.size) { + throw std::runtime_error {"Invalid operands, both registers need to be of the same size"}; + } + + put_dword(val.wide() << 31 | 0b11'11010010 << 21 | second.reg << 16 | uint32_t(condition) << 12 | 0b00 << 10 | val.reg << 5 | uint32_t(flags)); + } + + void BufferWriter::put_ccmn(Condition condition, Condition flags, Registry val, Registry second) { + if (val.size != second.size) { + throw std::runtime_error {"Invalid operands, both registers need to be of the same size"}; + } + + put_dword(val.wide() << 31 | 0b01'11010010 << 21 | second.reg << 16 | uint32_t(condition) << 12 | 0b00 << 10 | val.reg << 5 | uint32_t(flags)); + } + + void BufferWriter::put_csinv(Condition condition, Registry dst, Registry truthy, Registry falsy) { + put_inst_csinc(condition, dst, truthy, falsy, false, true); + } + + void BufferWriter::put_cinv(Condition condition, Registry dst, Registry src) { + put_inst_csinc(invert(condition), dst, src, src, false, true); + } + + void BufferWriter::put_csetm(Condition condition, Registry dst) { + Registry zero = dst.wide() ? XZR : WZR; + put_inst_csinc(invert(condition), dst, zero, zero, false, true); + } + + void BufferWriter::put_csneg(Condition condition, Registry dst, Registry truthy, Registry falsy) { + put_inst_csinc(condition, dst, truthy, falsy, true, true); + } + + void BufferWriter::put_cneg(Condition condition, Registry dst, Registry src) { + put_inst_csinc(condition, dst, src, src, true, true); + } + + void BufferWriter::put_cmn(Registry src, uint16_t imm12, bool shift_12) { + Registry zero = src.wide() ? XZR : WZR; + put_adds(zero, src, imm12, shift_12); + } + + void BufferWriter::put_cmn(Registry a, Registry b, ShiftType shift, uint8_t imm6) { + Registry zero = a.wide() ? XZR : WZR; + put_adds(zero, a, b, shift, imm6); + } + + void BufferWriter::put_cmp(Registry src, uint16_t imm12, bool shift_12) { + Registry zero = src.wide() ? XZR : WZR; + put_subs(zero, src, imm12, shift_12); + } + + void BufferWriter::put_cmp(Registry a, Registry b, ShiftType shift, uint8_t imm6) { + Registry zero = a.wide() ? XZR : WZR; + put_subs(zero, a, b, shift, imm6); + } + + void BufferWriter::put_ldxp(Registry r1, Registry r2, Registry src) { + put_inst_ldstx(r1, r2, XZR, src, 0b10 | r1.wide(), true, false, true); + } + + void BufferWriter::put_ldaxp(Registry r1, Registry r2, Registry src) { + put_inst_ldstx(r1, r2, XZR, src, 0b10 | r1.wide(), true, true, true); + } + + void BufferWriter::put_ldaxr(Registry dst, Registry src) { + put_inst_ldstx(dst, XZR, XZR, src, 0b10 | dst.wide(), true, true, false); + } + + void BufferWriter::put_ldaxrh(Registry dst, Registry src) { + put_inst_ldstx(dst, XZR, XZR, src, 0b01, true, true, false); + } + + void BufferWriter::put_ldaxrb(Registry dst, Registry src) { + put_inst_ldstx(dst, XZR, XZR, src, 0b00, true, true, false); + } + + void BufferWriter::put_ldxr(Registry dst, Registry src) { + put_inst_ldstx(dst, XZR, XZR, src, 0b10 | dst.wide(), true, false, false); + } + + void BufferWriter::put_ldxrh(Registry dst, Registry src) { + put_inst_ldstx(dst, XZR, XZR, src, 0b01, true, false, false); + } + + void BufferWriter::put_ldxrb(Registry dst, Registry src) { + put_inst_ldstx(dst, XZR, XZR, src, 0b00, true, false, false); + } + + void BufferWriter::put_stlxr(Registry status, Registry dst, Registry src) { + put_inst_ldstx(dst, XZR, status, src, 0b10 | dst.wide(), false, true, false); + } + + void BufferWriter::put_stlxrh(Registry status, Registry dst, Registry src) { + put_inst_ldstx(dst, XZR, status, src, 0b01, false, true, false); + } + + void BufferWriter::put_stlxrb(Registry status, Registry dst, Registry src) { + put_inst_ldstx(dst, XZR, status, src, 0b00, false, true, false); + } + + void BufferWriter::put_stxr(Registry status, Registry dst, Registry src) { + put_inst_ldstx(dst, XZR, status, src, 0b10 | dst.wide(), false, false, false); + } + + void BufferWriter::put_stxrh(Registry status, Registry dst, Registry src) { + put_inst_ldstx(dst, XZR, status, src, 0b01, false, false, false); + } + + void BufferWriter::put_stxrb(Registry status, Registry dst, Registry src) { + put_inst_ldstx(dst, XZR, status, src, 0b00, false, false, false); + } + + void BufferWriter::put_ldtr(Registry dst, Registry src, int16_t offset) { + put_inst_ldst_simm9(dst, src, offset, 0b10 | dst.wide(), true, 0b10); + } + + void BufferWriter::put_ldtrh(Registry dst, Registry src, int16_t offset) { + put_inst_ldst_simm9(dst, src, offset, 0b01, true, 0b10); + } + + void BufferWriter::put_ldtrb(Registry dst, Registry src, int16_t offset) { + put_inst_ldst_simm9(dst, src, offset, 0b00, true, 0b10); + } + + void BufferWriter::put_sttr(Registry dst, Registry src, int16_t offset) { + put_inst_ldst_simm9(dst, src, offset, 0b10 | dst.wide(), false, 0b10); + } + + void BufferWriter::put_sttrh(Registry dst, Registry src, int16_t offset) { + put_inst_ldst_simm9(dst, src, offset, 0b01, false, 0b10); + } + + void BufferWriter::put_sttrb(Registry dst, Registry src, int16_t offset) { + put_inst_ldst_simm9(dst, src, offset, 0b00, false, 0b10); + } + + void BufferWriter::put_ldur(Registry dst, Registry src, int16_t offset) { + put_inst_ldst_simm9(dst, src, offset, 0b10 | dst.wide(), true, 0b00); + } + + void BufferWriter::put_ldurh(Registry dst, Registry src, int16_t offset) { + put_inst_ldst_simm9(dst, src, offset, 0b01, true, 0b00); + } + + void BufferWriter::put_ldurb(Registry dst, Registry src, int16_t offset) { + put_inst_ldst_simm9(dst, src, offset, 0b00, true, 0b00); + } + + void BufferWriter::put_stur(Registry dst, Registry src, int16_t offset) { + put_inst_ldst_simm9(dst, src, offset, 0b10 | dst.wide(), false, 0b00); + } + + void BufferWriter::put_sturh(Registry dst, Registry src, int16_t offset) { + put_inst_ldst_simm9(dst, src, offset, 0b01, false, 0b00); + } + + void BufferWriter::put_sturb(Registry dst, Registry src, int16_t offset) { + put_inst_ldst_simm9(dst, src, offset, 0b00, false, 0b00); + } + + void BufferWriter::put_sxtw(Registry dst, Registry src) { + put_sbfm(dst, src, BitPattern::try_pack(0xffffffff)); + } + + void BufferWriter::put_sxth(Registry dst, Registry src) { + put_sbfm(dst, src, BitPattern::try_pack(0xffff)); + } + + void BufferWriter::put_sxtb(Registry dst, Registry src) { + put_sbfm(dst, src, BitPattern::try_pack(0xff)); + } + + void BufferWriter::put_prfm(Prefetch mode, Registry base, Registry index) { + // TODO: this is a partial implementation + put_dword(0b11'11'1000'10'1 << 21 | index.reg << 16 | 0b011 << 13 /* LSL */ | 0b010 << 10 | base.reg << 5 | static_cast(mode)); + } + + void BufferWriter::put_stlxp(Registry status, Registry r1, Registry r2, Registry src) { + put_inst_ldstx(r1, r2, status, src, 0b10 | r1.wide(), false, true, true); + } + + void BufferWriter::put_stxp(Registry status, Registry r1, Registry r2, Registry src) { + put_inst_ldstx(r2, r2, status, src, 0b10 | r1.wide(), false, false, true); + } + + void BufferWriter::put_inst_ldop(Registry val, Registry dst, Registry src, Order order, uint8_t size, uint32_t opc) { + if (!src.wide()) { + throw std::runtime_error {"Invalid operand, source and destination register must be wide"}; + } + + if (src.is(Registry::ZERO)) { + throw std::runtime_error {"Invalid operand, source can't be the zero register"}; + } + + if (!val.is(Registry::GENERAL) || !dst.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid operand, value and destination must be general purpose registers"}; + } + + uint32_t a = is_order_acquire(order) ? (1 << 23) : 0; + uint32_t r = is_order_release(order) ? (1 << 22) : 0; + put_dword(size << 30 | 0b111000'00'1 << 21 | val.reg << 16 | opc << 12 | src.reg << 5 | dst.reg | a | r); + } + + void BufferWriter::put_ldaddb(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b00, 0b000); + } + + void BufferWriter::put_ldaddh(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b01, 0b000); + } + + void BufferWriter::put_ldadd(Registry val, Registry dst, Registry src, Order order) { + if (val.wide() != dst.wide()) { + throw std::runtime_error {"Invalid operand, value and destination need to be of the same size"}; + } + + put_inst_ldop(val, dst, src, order, 0b10 | val.wide(), 0b000); + } + + void BufferWriter::put_ldclrb(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b00, 0b001); + } + + void BufferWriter::put_ldclrh(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b01, 0b001); + } + + void BufferWriter::put_ldclr(Registry val, Registry dst, Registry src, Order order) { + if (val.wide() != dst.wide()) { + throw std::runtime_error {"Invalid operand, value and destination need to be of the same size"}; + } + + put_inst_ldop(val, dst, src, order, 0b10 | val.wide(), 0b001); + } + + void BufferWriter::put_ldeorb(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b00, 0b010); + } + + void BufferWriter::put_ldeorh(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b01, 0b010); + } + + void BufferWriter::put_ldeor(Registry val, Registry dst, Registry src, Order order) { + if (val.wide() != dst.wide()) { + throw std::runtime_error {"Invalid operand, value and destination need to be of the same size"}; + } + + put_inst_ldop(val, dst, src, order, 0b10 | val.wide(), 0b010); + } + + void BufferWriter::put_ldsetb(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b00, 0b011); + } + + void BufferWriter::put_ldseth(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b01, 0b011); + } + + void BufferWriter::put_ldset(Registry val, Registry dst, Registry src, Order order) { + if (val.wide() != dst.wide()) { + throw std::runtime_error {"Invalid operand, value and destination need to be of the same size"}; + } + + put_inst_ldop(val, dst, src, order, 0b10 | val.wide(), 0b011); + } + + void BufferWriter::put_ldsmaxb(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b00, 0b100); + } + + void BufferWriter::put_ldsmaxh(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b01, 0b100); + } + + void BufferWriter::put_ldsmax(Registry val, Registry dst, Registry src, Order order) { + if (val.wide() != dst.wide()) { + throw std::runtime_error {"Invalid operand, value and destination need to be of the same size"}; + } + + put_inst_ldop(val, dst, src, order, 0b10 | val.wide(), 0b100); + } + + void BufferWriter::put_ldumaxb(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b00, 0b110); + } + + void BufferWriter::put_ldumaxh(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b01, 0b110); + } + + void BufferWriter::put_ldumax(Registry val, Registry dst, Registry src, Order order) { + if (val.wide() != dst.wide()) { + throw std::runtime_error {"Invalid operand, value and destination need to be of the same size"}; + } + + put_inst_ldop(val, dst, src, order, 0b10 | val.wide(), 0b110); + } + + void BufferWriter::put_ldsminb(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b00, 0b101); + } + + void BufferWriter::put_ldsminh(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b01, 0b101); + } + + void BufferWriter::put_ldsmin(Registry val, Registry dst, Registry src, Order order) { + if (val.wide() != dst.wide()) { + throw std::runtime_error {"Invalid operand, value and destination need to be of the same size"}; + } + + put_inst_ldop(val, dst, src, order, 0b10 | val.wide(), 0b101); + } + + void BufferWriter::put_lduminb(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b00, 0b111); + } + + void BufferWriter::put_lduminh(Registry val, Registry dst, Registry src, Order order) { + put_inst_ldop(val, dst, src, order, 0b01, 0b111); + } + + void BufferWriter::put_ldumin(Registry val, Registry dst, Registry src, Order order) { + if (val.wide() != dst.wide()) { + throw std::runtime_error {"Invalid operand, value and destination need to be of the same size"}; + } + + put_inst_ldop(val, dst, src, order, 0b10 | val.wide(), 0b111); + } + + void BufferWriter::put_staddb(Registry val, Registry ptr, Order order) { + put_ldaddb(val, WZR, ptr, order); + } + + void BufferWriter::put_staddh(Registry val, Registry ptr, Order order) { + put_ldaddh(val, WZR, ptr, order); + } + + void BufferWriter::put_stadd(Registry val, Registry ptr, Order order) { + put_ldadd(val, val.wide() ? XZR : WZR, ptr, order); + } + + void BufferWriter::put_stclrb(Registry val, Registry ptr, Order order) { + put_ldclrb(val, WZR, ptr, order); + } + + void BufferWriter::put_stclrh(Registry val, Registry ptr, Order order) { + put_ldclrh(val, WZR, ptr, order); + } + + void BufferWriter::put_stclr(Registry val, Registry ptr, Order order) { + put_ldclr(val, val.wide() ? XZR : WZR, ptr, order); + } + + void BufferWriter::put_steorb(Registry val, Registry ptr, Order order) { + put_ldeorb(val, WZR, ptr, order); + } + + void BufferWriter::put_steorh(Registry val, Registry ptr, Order order) { + put_ldeorh(val, WZR, ptr, order); + } + + void BufferWriter::put_steor(Registry val, Registry ptr, Order order) { + put_ldeor(val, val.wide() ? XZR : WZR, ptr, order); + } + + void BufferWriter::put_stsetb(Registry val, Registry ptr, Order order) { + put_ldsetb(val, WZR, ptr, order); + } + + void BufferWriter::put_stseth(Registry val, Registry ptr, Order order) { + put_ldseth(val, WZR, ptr, order); + } + + void BufferWriter::put_stset(Registry val, Registry ptr, Order order) { + put_ldset(val, val.wide() ? XZR : WZR, ptr, order); + } + + void BufferWriter::put_stsmaxb(Registry val, Registry ptr, Order order) { + put_ldsmaxb(val, WZR, ptr, order); + } + + void BufferWriter::put_stsmaxh(Registry val, Registry ptr, Order order) { + put_ldsmaxh(val, WZR, ptr, order); + } + + void BufferWriter::put_stsmax(Registry val, Registry ptr, Order order) { + put_ldsmax(val, val.wide() ? XZR : WZR, ptr, order); + } + + void BufferWriter::put_stumaxb(Registry val, Registry ptr, Order order) { + put_ldumaxb(val, WZR, ptr, order); + } + + void BufferWriter::put_stumaxh(Registry val, Registry ptr, Order order) { + put_ldumaxh(val, WZR, ptr, order); + } + + void BufferWriter::put_stumax(Registry val, Registry ptr, Order order) { + put_ldumax(val, val.wide() ? XZR : WZR, ptr, order); + } + + void BufferWriter::put_stsminb(Registry val, Registry ptr, Order order) { + put_ldsminb(val, WZR, ptr, order); + } + + void BufferWriter::put_stsminh(Registry val, Registry ptr, Order order) { + put_ldsminh(val, WZR, ptr, order); + } + + void BufferWriter::put_stsmin(Registry val, Registry ptr, Order order) { + put_ldsmin(val, val.wide() ? XZR : WZR, ptr, order); + } + + void BufferWriter::put_stuminb(Registry val, Registry ptr, Order order) { + put_lduminb(val, WZR, ptr, order); + } + + void BufferWriter::put_stuminh(Registry val, Registry ptr, Order order) { + put_lduminh(val, WZR, ptr, order); + } + + void BufferWriter::put_stumin(Registry val, Registry ptr, Order order) { + put_ldumin(val, val.wide() ? XZR : WZR, ptr, order); + } + + void BufferWriter::put_hint(uint8_t imm7) { + put_dword(0b1101010100'0'00'011'0010 << 12 | (0b1111'111 & imm7) << 5 | 0b11111); + } + + void BufferWriter::put_hlt(uint16_t imm16) { + put_dword(0b11010100'010 << 21 | imm16 << 5 | 0b000'00); + } + + void BufferWriter::put_hvc(uint16_t imm16) { + put_dword(0b11010100'000 << 21 | imm16 << 5 | 0b000'10); + } + + void BufferWriter::put_smc(uint16_t imm16) { + put_dword(0b11010100'000 << 21 | imm16 << 5 | 0b000'11); + } + + void BufferWriter::put_brk(uint16_t imm) { + put_dword(0b11010100'001 << 21 | imm << 5 | 0b00000); + } + + void BufferWriter::put_isb() { + put_dword(0b1101010100'0'00'011'0011 << 12 | 0b1111 << 8 | 0b1'10'11111); + } + + void BufferWriter::put_nop() { + put_hint(0b0000'000); + } + + void BufferWriter::put_yield() { + put_hint(0b0000'001); + } + + void BufferWriter::put_wfe() { + put_hint(0b0000'010); + } + + void BufferWriter::put_wfi() { + put_hint(0b0000'011); + } + + void BufferWriter::put_sev() { + put_hint(0b0000'100); + } + + void BufferWriter::put_sevl() { + put_hint(0b0000'101); + } + + void BufferWriter::put_esb() { + put_hint(0b0010'000); + } + + void BufferWriter::put_psb() { + put_hint(0b0010'001); + } + +} diff --git a/src/asm/aarch64/instructions/branch.cpp b/src/asmio/aarch64/writer_branch.cpp similarity index 97% rename from src/asm/aarch64/instructions/branch.cpp rename to src/asmio/aarch64/writer_branch.cpp index 0dd9ced..aca0d5c 100644 --- a/src/asm/aarch64/instructions/branch.cpp +++ b/src/asmio/aarch64/writer_branch.cpp @@ -1,6 +1,6 @@ -#include "../writer.hpp" -#include "out/buffer/linkage.hpp" +#include "writer.hpp" +#include namespace asmio::arm { diff --git a/src/out/elf/dwarf/abbrev.cpp b/src/asmio/elf/dwarf/abbrev.cpp similarity index 100% rename from src/out/elf/dwarf/abbrev.cpp rename to src/asmio/elf/dwarf/abbrev.cpp diff --git a/src/out/elf/dwarf/abbrev.hpp b/src/asmio/elf/dwarf/abbrev.hpp similarity index 95% rename from src/out/elf/dwarf/abbrev.hpp rename to src/asmio/elf/dwarf/abbrev.hpp index bc0514a..ea3a71e 100644 --- a/src/out/elf/dwarf/abbrev.hpp +++ b/src/asmio/elf/dwarf/abbrev.hpp @@ -1,10 +1,10 @@ #pragma once #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include "attribute.hpp" #include "form.hpp" diff --git a/src/out/elf/dwarf/attribute.hpp b/src/asmio/elf/dwarf/attribute.hpp similarity index 99% rename from src/out/elf/dwarf/attribute.hpp rename to src/asmio/elf/dwarf/attribute.hpp index 2f33e1a..de19117 100644 --- a/src/out/elf/dwarf/attribute.hpp +++ b/src/asmio/elf/dwarf/attribute.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include namespace asmio { diff --git a/src/out/elf/dwarf/emitter.cpp b/src/asmio/elf/dwarf/emitter.cpp similarity index 100% rename from src/out/elf/dwarf/emitter.cpp rename to src/asmio/elf/dwarf/emitter.cpp diff --git a/src/out/elf/dwarf/emitter.hpp b/src/asmio/elf/dwarf/emitter.hpp similarity index 85% rename from src/out/elf/dwarf/emitter.hpp rename to src/asmio/elf/dwarf/emitter.hpp index 67a03ae..2a19d93 100644 --- a/src/out/elf/dwarf/emitter.hpp +++ b/src/asmio/elf/dwarf/emitter.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include namespace asmio { diff --git a/src/out/elf/dwarf/encoding.hpp b/src/asmio/elf/dwarf/encoding.hpp similarity index 95% rename from src/out/elf/dwarf/encoding.hpp rename to src/asmio/elf/dwarf/encoding.hpp index 4ede892..73460a0 100644 --- a/src/out/elf/dwarf/encoding.hpp +++ b/src/asmio/elf/dwarf/encoding.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace asmio { diff --git a/src/out/elf/dwarf/form.hpp b/src/asmio/elf/dwarf/form.hpp similarity index 100% rename from src/out/elf/dwarf/form.hpp rename to src/asmio/elf/dwarf/form.hpp diff --git a/src/out/elf/dwarf/info.cpp b/src/asmio/elf/dwarf/info.cpp similarity index 100% rename from src/out/elf/dwarf/info.cpp rename to src/asmio/elf/dwarf/info.cpp diff --git a/src/out/elf/dwarf/info.hpp b/src/asmio/elf/dwarf/info.hpp similarity index 95% rename from src/out/elf/dwarf/info.hpp rename to src/asmio/elf/dwarf/info.hpp index ea18c99..dcd721b 100644 --- a/src/out/elf/dwarf/info.hpp +++ b/src/asmio/elf/dwarf/info.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include "abbrev.hpp" namespace asmio { diff --git a/src/out/elf/dwarf/lang.hpp b/src/asmio/elf/dwarf/lang.hpp similarity index 100% rename from src/out/elf/dwarf/lang.hpp rename to src/asmio/elf/dwarf/lang.hpp diff --git a/src/out/elf/dwarf/lines.cpp b/src/asmio/elf/dwarf/lines.cpp similarity index 100% rename from src/out/elf/dwarf/lines.cpp rename to src/asmio/elf/dwarf/lines.cpp diff --git a/src/out/elf/dwarf/lines.hpp b/src/asmio/elf/dwarf/lines.hpp similarity index 99% rename from src/out/elf/dwarf/lines.hpp rename to src/asmio/elf/dwarf/lines.hpp index 7f65fb1..34950d7 100644 --- a/src/out/elf/dwarf/lines.hpp +++ b/src/asmio/elf/dwarf/lines.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include "emitter.hpp" diff --git a/src/out/elf/dwarf/tag.hpp b/src/asmio/elf/dwarf/tag.hpp similarity index 100% rename from src/out/elf/dwarf/tag.hpp rename to src/asmio/elf/dwarf/tag.hpp diff --git a/src/out/elf/dwarf/unit.hpp b/src/asmio/elf/dwarf/unit.hpp similarity index 100% rename from src/out/elf/dwarf/unit.hpp rename to src/asmio/elf/dwarf/unit.hpp diff --git a/src/out/elf/dwarf/version.hpp b/src/asmio/elf/dwarf/version.hpp similarity index 100% rename from src/out/elf/dwarf/version.hpp rename to src/asmio/elf/dwarf/version.hpp diff --git a/src/out/elf/export.cpp b/src/asmio/elf/export.cpp similarity index 81% rename from src/out/elf/export.cpp rename to src/asmio/elf/export.cpp index cd2a71e..ed7f4de 100644 --- a/src/out/elf/export.cpp +++ b/src/asmio/elf/export.cpp @@ -1,10 +1,16 @@ #include "export.hpp" #include "dwarf/lines.hpp" -#include "out/buffer/segmented.hpp" +#include +#include namespace asmio { + struct MappingInfo { + ElfModel::Section* section; + ElfSymbolType content; + }; + static void export_line_data(ElfModel& model, SegmentedBuffer& segmented) { // don't create the line emitter if we have no lines @@ -28,18 +34,57 @@ namespace asmio { } emitter.end_sequence(); + } + + static void export_defined_symbols(ElfModel& model, SegmentedBuffer& segmented, std::unordered_map& section_map) { + for (const ExportSymbol& symbol : segmented.exports()) { + const Label& label = symbol.label; + + if (!label.is_text()) { + continue; + } + + BufferMarker marker = segmented.get_label(label); + MappingInfo info = section_map[marker.section]; + + ElfSymbolBinding binding = ElfSymbolBinding::GLOBAL; + ElfSymbolVisibility visibility = ElfSymbolVisibility::DEFAULT; + + switch (symbol.type) { + + case ExportSymbol::PRIVATE: + binding = ElfSymbolBinding::LOCAL; + visibility = ElfSymbolVisibility::HIDDEN; + break; + + case ExportSymbol::PUBLIC: + binding = ElfSymbolBinding::GLOBAL; + visibility = ElfSymbolVisibility::PROTECTED; + break; + + case ExportSymbol::WEAK: + binding = ElfSymbolBinding::WEAK; + visibility = ElfSymbolVisibility::PROTECTED; + break; + } + + model.symbol(info.content, label.view(), binding, visibility, info.section, marker.offset, symbol.size); + } } - ElfModel to_elf(SegmentedBuffer& segmented, const Label& entry, uint64_t address, const LinkReporter& handler) { + static void export_undefined_symbols(ElfModel& model, std::vector& symbols, std::unordered_map& section_map) { + for (auto& linkage : symbols) { + auto mapping = section_map[linkage.target.section]; + ElfModel::Symbol* symbol = model.symbol(mapping.content, linkage.label.view(), ElfSymbolBinding::GLOBAL, ElfSymbolVisibility::DEFAULT, nullptr, 0, 0); + model.relocation(linkage.type.relocation, symbol, mapping.section, linkage.target.offset, linkage.addend); + } + } - struct MappingInfo { - ElfModel::Section* section; - ElfSymbolType content; - }; + ElfModel to_elf(SegmentedBuffer& segmented, const Label& entry, uint64_t address, const LinkReporter& handler) { // after alignment, we will know how big the buffer needs to be - const size_t page = getpagesize(); + const size_t page = page_size(); segmented.align(page); auto unresolved = segmented.link(address, handler); @@ -77,59 +122,21 @@ namespace asmio { auto* elf_section = model.section(ElfSectionType::PROGBITS, segment.name, elf_segment, address, 1, 0, segment.flags.to_elf_section()); elf_section_buffer = elf_section->buffer; - const ElfSymbolType content = segment.flags.to_elf_symbol(); - section_map[segment.index] = {elf_section, content}; + const ElfSymbolType resident = segment.flags.to_elf_symbol(); + section_map[segment.index] = {elf_section, resident}; } elf_section_buffer->write(segment.buffer); elf_segment->buffer->push(segment.tail); } - for (const ExportSymbol& symbol : segmented.exports()) { - const Label& label = symbol.label; - - if (!label.is_text()) { - continue; - } - - BufferMarker marker = segmented.get_label(label); - MappingInfo info = section_map[marker.section]; - - ElfSymbolBinding binding = ElfSymbolBinding::GLOBAL; - ElfSymbolVisibility visibility = ElfSymbolVisibility::DEFAULT; - - switch (symbol.type) { - - case ExportSymbol::PRIVATE: - binding = ElfSymbolBinding::LOCAL; - visibility = ElfSymbolVisibility::HIDDEN; - break; - - case ExportSymbol::PUBLIC: - binding = ElfSymbolBinding::GLOBAL; - visibility = ElfSymbolVisibility::PROTECTED; - break; - - case ExportSymbol::WEAK: - binding = ElfSymbolBinding::WEAK; - visibility = ElfSymbolVisibility::PROTECTED; - break; - - } - - model.symbol(info.content, label.view(), binding, visibility, info.section, marker.offset, symbol.size); - } - - for (auto& linkage : unresolved) { - auto mapping = section_map[linkage.target.section]; - ElfModel::Symbol* symbol = model.symbol(mapping.content, linkage.label.view(), ElfSymbolBinding::GLOBAL, ElfSymbolVisibility::DEFAULT, nullptr, 0, 0); - model.relocation(linkage.type.relocation, symbol, mapping.section, linkage.target.offset, linkage.addend); - + if (create_sections) { + export_defined_symbols(model, segmented, section_map); + export_undefined_symbols(model, unresolved, section_map); + export_line_data(model, segmented); } - export_line_data(model, segmented); - return model; } -} \ No newline at end of file +} diff --git a/src/out/elf/export.hpp b/src/asmio/elf/export.hpp similarity index 85% rename from src/out/elf/export.hpp rename to src/asmio/elf/export.hpp index ae65ae8..198c7c3 100644 --- a/src/out/elf/export.hpp +++ b/src/asmio/elf/export.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include "object.hpp" #include "model.hpp" diff --git a/src/out/elf/header.hpp b/src/asmio/elf/header.hpp similarity index 76% rename from src/out/elf/header.hpp rename to src/asmio/elf/header.hpp index db4968d..1000c81 100644 --- a/src/out/elf/header.hpp +++ b/src/asmio/elf/header.hpp @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include namespace asmio { @@ -46,13 +46,32 @@ namespace asmio { LSB = 1, // Two's complement, little-endian. MSB = 2, // Two's complement, big-endian. }; + + enum struct ElfAbi : uint8_t { + NONE = 0, ///< UNIX System V ABI + SYSV = 0, ///< Alias. + HPUX = 1, ///< HP-UX + NETBSD = 2, ///< NetBSD. + GNU = 3, ///< Object uses GNU ELF extensions. + LINUX = 3, ///< Compatibility alias. + SOLARIS = 6, ///< Sun Solaris. + AIX = 7, ///< IBM AIX. + IRIX = 8, ///< SGI Irix. + FREEBSD = 9, ///< FreeBSD. + TRU64 = 10, ///< Compaq TRU64 UNIX. + MODESTO = 11, ///< Novell Modesto. + OPENBSD = 12, ///< OpenBSD. + ARM_AEABI = 64, ///< ARM EABI + ARM = 97, ///< ARM + STANDALONE = 255, ///< Standalone (embedded) application + }; struct PACKED ElfIdentification { uint8_t magic[4]; ///< Magic number identifying the file as an ELF object file ElfClass clazz; ///< Size of basic data types ElfData data; ///< Endianness uint8_t version; ///< Same as FileHeader->version - uint8_t abi; ///< Target operating system + ElfAbi abi; ///< Target operating system uint8_t abi_version; ///< Version of the ABI to which the object is targeted uint8_t pad[7]; ///< Must all be 0 }; diff --git a/src/out/elf/model.cpp b/src/asmio/elf/model.cpp similarity index 97% rename from src/out/elf/model.cpp rename to src/asmio/elf/model.cpp index 22e0f4e..da6d977 100644 --- a/src/out/elf/model.cpp +++ b/src/asmio/elf/model.cpp @@ -1,5 +1,7 @@ #include "model.hpp" +#include + namespace asmio { /* @@ -58,7 +60,7 @@ namespace asmio { } ElfModel::Segment* ElfModel::segment(ElfSegmentType type, uint32_t flags, uint64_t address, uint64_t tail) { - const int alignment = getpagesize(); + const int alignment = page_size(); Segment segment {}; segment.type = type; @@ -103,7 +105,7 @@ namespace asmio { strings.type = ElfSectionType::STRTAB; strings.name = section_strings.append(".strtab"); strings.alignment = 1; - strings.buffer = section_buffer_pool->chunk(); + strings.buffer = section_buffer_pool->chunk(strings.alignment); symbol_string_table = define_section(strings); Section symtab {}; @@ -113,7 +115,7 @@ namespace asmio { symtab.alignment = 8; symtab.link = LinkInfo(symbol_string_table); symtab.info = LinkInfo(); // This is set during baking, when we know how many local symbols are there - symtab.buffer = section_buffer_pool->chunk(); + symtab.buffer = section_buffer_pool->chunk(symtab.alignment); symbol_table = define_section(symtab); Symbol symbol {}; @@ -190,7 +192,7 @@ namespace asmio { ident.clazz = ElfClass::BIT_64; ident.data = ElfData::LSB; ident.version = ELF_VERSION; - ident.abi = 0; // TODO: move to enum + ident.abi = ElfAbi::SYSV; ident.abi_version = 0; memset(ident.pad, 0, sizeof(ident.pad)); @@ -277,4 +279,4 @@ namespace asmio { return {elf.bake()}; } -} \ No newline at end of file +} diff --git a/src/out/elf/model.hpp b/src/asmio/elf/model.hpp similarity index 96% rename from src/out/elf/model.hpp rename to src/asmio/elf/model.hpp index 265f2c6..b386200 100644 --- a/src/out/elf/model.hpp +++ b/src/asmio/elf/model.hpp @@ -1,7 +1,8 @@ #pragma once -#include -#include -#include + +#include +#include +#include #include "object.hpp" #include "header.hpp" @@ -84,7 +85,7 @@ namespace asmio { private: - static constexpr int ELF_VERSION = 1; + constexpr static int ELF_VERSION = 1; Indexer section_indexer; diff --git a/src/asmio/elf/object.cpp b/src/asmio/elf/object.cpp new file mode 100644 index 0000000..58c4c3d --- /dev/null +++ b/src/asmio/elf/object.cpp @@ -0,0 +1,73 @@ + +#include "object.hpp" + +#include +#include + +namespace asmio { + + std::ostream& operator<<(std::ostream& os, RunStatus c) { + switch (c) { + case RunStatus::SUCCESS: return os << "SUCCESS"; + case RunStatus::ERROR: return os << "ERROR"; + default: return os << "INVALID"; + } + } + + std::ostream& operator<<(std::ostream& os, const RunResult& result) { + return os << "RunResult{status=" << result.type << ", return=" << result.status << "}"; + } + + /* + * class ObjectFile + */ + + ObjectFile::ObjectFile(std::vector&& image) + : image(std::move(image)) { + } + + ObjectFile::ObjectFile(const std::vector& image) + : image(image) { + } + + bool ObjectFile::save(const std::string& path) const { + using std::filesystem::perms; + + // if file creation fails return false + try { + std::fstream output {path, std::ios::out | std::ios::trunc | std::ios::binary}; + + if (output.bad()) { + return false; + } + + output.write(reinterpret_cast(image.data()), image.size()); + output.close(); + } catch (const std::exception&) { + return false; + } + + // this part is best-effort only + try { + const perms flags = perms::owner_exec | perms::group_exec | perms::others_exec; + std::filesystem::permissions(path, flags, std::filesystem::perm_options::add); + } catch (const std::exception&) {} + + // file was created + return true; + } + + const std::vector& ObjectFile::bytes() const { + return image; + } + + RunResult ObjectFile::execute(const char* name) const { + const char* argv[] = {name, nullptr}; + return execute(argv, const_cast(environ)); + } + + RunResult ObjectFile::execute(const char** argv, const char** envp) const { + return run_file_image(image.data(), image.size(), argv, envp); + } + +} diff --git a/src/out/elf/object.hpp b/src/asmio/elf/object.hpp similarity index 68% rename from src/out/elf/object.hpp rename to src/asmio/elf/object.hpp index 5fa4c2f..8b8c33b 100644 --- a/src/out/elf/object.hpp +++ b/src/asmio/elf/object.hpp @@ -1,34 +1,30 @@ #pragma once -#include "external.hpp" +#include #include "dwarf/abbrev.hpp" #define DEFAULT_ELF_MOUNT 0x08048000 namespace asmio { - template - constexpr static auto supply = [] noexcept { return V; }; - enum struct RunStatus { - SUCCESS, ///< elf file was executed - ARGS_ERROR, ///< the given arguments are invalid - MEMFD_ERROR, ///< memfd failed - MMAP_ERROR, ///< mmap failed - SEAL_ERROR, ///< fcntl failed - STAT_ERROR, ///< fstat failed - FORK_ERROR, ///< fork failed - EXEC_ERROR, ///< file not executable - WAIT_ERROR, ///< waitpid failed + SUCCESS, + ERROR, }; struct RunResult { - RunResult(RunStatus type) - : type(type), status(0) { + constexpr RunResult(RunStatus type, int status) + : type(type), status(status) { + } + + /// Create result with a specific error code + constexpr static RunResult error(int error_status) { + return {RunStatus::ERROR, error_status}; } - RunResult(int status) - : type(RunStatus::SUCCESS), status(status) { + /// Create a result with a specific return code + constexpr static RunResult success(int return_status) { + return {RunStatus::SUCCESS, return_status}; } const RunStatus type; diff --git a/src/out/elf/relocation.hpp b/src/asmio/elf/relocation.hpp similarity index 99% rename from src/out/elf/relocation.hpp rename to src/asmio/elf/relocation.hpp index 91fc0e2..839e1eb 100644 --- a/src/out/elf/relocation.hpp +++ b/src/asmio/elf/relocation.hpp @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include namespace asmio { diff --git a/src/out/elf/section.hpp b/src/asmio/elf/section.hpp similarity index 96% rename from src/out/elf/section.hpp rename to src/asmio/elf/section.hpp index 8be72ca..702deff 100644 --- a/src/out/elf/section.hpp +++ b/src/asmio/elf/section.hpp @@ -1,8 +1,7 @@ #pragma once -#include -#include -#include +#include +#include namespace asmio { diff --git a/src/out/elf/segment.hpp b/src/asmio/elf/segment.hpp similarity index 95% rename from src/out/elf/segment.hpp rename to src/asmio/elf/segment.hpp index d38f84e..58e3619 100644 --- a/src/out/elf/segment.hpp +++ b/src/asmio/elf/segment.hpp @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include namespace asmio { diff --git a/src/out/elf/symbol.hpp b/src/asmio/elf/symbol.hpp similarity index 92% rename from src/out/elf/symbol.hpp rename to src/asmio/elf/symbol.hpp index c95fe0a..32c1c54 100644 --- a/src/out/elf/symbol.hpp +++ b/src/asmio/elf/symbol.hpp @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include enum struct ElfSymbolVisibility : uint8_t { DEFAULT = 0, ///< Visible to other modules, local references can use symbols from other modules @@ -33,4 +33,6 @@ struct PACKED ElfSymbol { uint16_t shndx = 0; ///< Symbol's section uint64_t value = 0; ///< Value (e.g. address, offset) of a symbol uint64_t ssize = 0; ///< Size of the symbol, or zero if not applicable -}; \ No newline at end of file +}; + +static_assert(sizeof(ElfSymbol) == 3*8); \ No newline at end of file diff --git a/src/external.hpp b/src/asmio/external.hpp similarity index 78% rename from src/external.hpp rename to src/asmio/external.hpp index 22ee9ba..17a895d 100644 --- a/src/external.hpp +++ b/src/asmio/external.hpp @@ -22,17 +22,9 @@ #include #include #include - -// systems -#ifdef __linux__ -# include -# include -# include -# include -# include -#else -# error "Non-linux platforms not yet suported!" -#endif +#include +#include +#include #define NOT(expr) (!(expr)) diff --git a/src/asm/module.cpp b/src/asmio/module.cpp similarity index 98% rename from src/asm/module.cpp rename to src/asmio/module.cpp index 420f6df..0aeb07f 100644 --- a/src/asm/module.cpp +++ b/src/asmio/module.cpp @@ -1,7 +1,7 @@ #include "module.hpp" -#include -#include +#include +#include #include #include diff --git a/src/asm/module.hpp b/src/asmio/module.hpp similarity index 95% rename from src/asm/module.hpp rename to src/asmio/module.hpp index 771233d..08ac242 100644 --- a/src/asm/module.hpp +++ b/src/asmio/module.hpp @@ -1,9 +1,7 @@ #pragma once -#include -#include -#include -#include "util.hpp" +#include +#include // TODO decouple from tasml, those classes should be moved to asmio namespace tasml { diff --git a/src/out/buffer/executable.cpp b/src/asmio/program/executable.cpp similarity index 84% rename from src/out/buffer/executable.cpp rename to src/asmio/program/executable.cpp index a1c317b..711ad19 100644 --- a/src/out/buffer/executable.cpp +++ b/src/asmio/program/executable.cpp @@ -1,6 +1,8 @@ #include "executable.hpp" +#include + namespace asmio { /* @@ -9,13 +11,13 @@ namespace asmio { ExecutableBuffer::ExecutableBuffer(size_t total) { - const size_t page = getpagesize(); + const size_t page = page_size(); // this value should already be page aligned, but let's check anyway length = util::align_up(total, page); // create a basic memory map, after this we will set the correct flags for each segment - buffer = (uint8_t*) mmap(nullptr, length, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + buffer = (uint8_t*) allocate_pages(length, MemoryFlag::W); if (buffer == nullptr) { throw std::runtime_error {"Failed to allocate memory map!"}; @@ -32,13 +34,13 @@ namespace asmio { ExecutableBuffer::ExecutableBuffer(const ExecutableBuffer& other) { labels = other.labels; - buffer = (uint8_t*) mmap(nullptr, other.length, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + buffer = (uint8_t*) allocate_pages(other.length, MemoryFlag::W); length = other.length; } ExecutableBuffer::~ExecutableBuffer() { if (buffer != nullptr) { - munmap(buffer, length); + free_pages(buffer, length); } buffer = nullptr; @@ -71,7 +73,7 @@ namespace asmio { memcpy(data, segment.buffer.data(), bytes); memset(data + bytes, segment.padder, segment.tail); - mprotect(data, segment.size(), segment.flags.to_mprotect()); + protect_pages(data, segment.size(), segment.flags); } // copy the label map @@ -97,10 +99,10 @@ namespace asmio { ExecutableBuffer to_executable(SegmentedBuffer& segmented) { - const size_t page = getpagesize(); + const size_t page = page_size(); segmented.align(page); - // after alignment we know how big the buffer needs to be + // after alignment, we know how big the buffer needs to be ExecutableBuffer buffer {segmented.total()}; // now that we have a buffer allocated we can link diff --git a/src/out/buffer/executable.hpp b/src/asmio/program/executable.hpp similarity index 84% rename from src/out/buffer/executable.hpp rename to src/asmio/program/executable.hpp index 4641eaa..49741df 100644 --- a/src/out/buffer/executable.hpp +++ b/src/asmio/program/executable.hpp @@ -1,6 +1,6 @@ #pragma once -#include "external.hpp" +#include #include "segmented.hpp" #include "label.hpp" @@ -13,13 +13,22 @@ "mm0","mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm6", \ "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)" +#if ARCH_X86 +# ifdef _WIN32 +# define SCALL_REG RCX +# endif +# ifdef __linux__ +# define SCALL_REG RDI +# endif +#endif + namespace asmio { template concept trivially_copyable = std::is_trivially_copyable_v; template - concept integral_or_void = std::is_integral_v || std::is_void_v; + concept trivially_returnable = std::is_integral_v || std::is_void_v || std::is_floating_point_v; class ExecutableBuffer { @@ -54,7 +63,7 @@ namespace asmio { public: - template + template R scall(size_t offset, Args... args) { // unpack parameter pack @@ -76,7 +85,7 @@ namespace asmio { return CALL_POINTER(offset, R, params); } - template + template R scall(const Label& label, Args... args) { return scall(labels.at(label), args...); } @@ -105,7 +114,11 @@ namespace asmio { return CALL_POINTER(offset, int32_t, nullptr); } - float call_f32(uint64_t offset = 0) { + float call_f32(uint32_t offset = 0) { + return CALL_POINTER(offset, float, nullptr); + } + + float call_f80(uint64_t offset = 0) { #if ARCH_X86 auto function = buffer + offset; volatile float tmp; @@ -113,7 +126,7 @@ namespace asmio { return tmp; #endif - throw std::runtime_error {"Float calls are unimplemented!"}; + throw std::runtime_error {"80 bit float calls are not supported!"}; } /* @@ -140,6 +153,10 @@ namespace asmio { return call_i32(labels.at(label)); } + float call_f80(const Label& label) { + return call_f80(labels.at(label)); + } + float call_f32(const Label& label) { return call_f32(labels.at(label)); } diff --git a/src/out/buffer/label.cpp b/src/asmio/program/label.cpp similarity index 100% rename from src/out/buffer/label.cpp rename to src/asmio/program/label.cpp diff --git a/src/out/buffer/label.hpp b/src/asmio/program/label.hpp similarity index 96% rename from src/out/buffer/label.hpp rename to src/asmio/program/label.hpp index 91f6356..0ca4402 100644 --- a/src/out/buffer/label.hpp +++ b/src/asmio/program/label.hpp @@ -1,10 +1,8 @@ #pragma once -#include - -#include "external.hpp" -#include "asm/util.hpp" -#include "util/refcnt.hpp" +#include +#include +#include namespace asmio { @@ -64,8 +62,9 @@ namespace asmio { : id(0), allocated(false), length(0), hash(0) { } - constexpr Label(nullptr_t) + constexpr Label(std::nullptr_t ignored) : Label() { + (void) ignored; } constexpr Label(Label&& label) noexcept diff --git a/src/out/buffer/linkage.cpp b/src/asmio/program/linkage.cpp similarity index 100% rename from src/out/buffer/linkage.cpp rename to src/asmio/program/linkage.cpp diff --git a/src/out/buffer/linkage.hpp b/src/asmio/program/linkage.hpp similarity index 100% rename from src/out/buffer/linkage.hpp rename to src/asmio/program/linkage.hpp diff --git a/src/out/buffer/memory.cpp b/src/asmio/program/memory.cpp similarity index 50% rename from src/out/buffer/memory.cpp rename to src/asmio/program/memory.cpp index a42c518..18ad360 100644 --- a/src/out/buffer/memory.cpp +++ b/src/asmio/program/memory.cpp @@ -1,6 +1,7 @@ #include "memory.hpp" -#include -#include + +#include +#include namespace asmio { @@ -10,12 +11,25 @@ namespace asmio { int MemoryFlags::to_mprotect() const { int protect = 0; - if (r) protect |= PROT_READ; - if (w) protect |= PROT_WRITE; - if (x) protect |= PROT_EXEC; + if (r) protect |= 0x1; // PROT_READ + if (w) protect |= 0x2; // PROT_WRITE + if (x) protect |= 0x4; // PROT_EXEC return protect; } + uint32_t MemoryFlags::to_win32() const { + // this gotta be the dumbest way to implement memory permissions... + if (x && !r && !w) return 0x10; // PAGE_EXECUTE + if (x && r && !w) return 0x20; // PAGE_EXECUTE_READ + if (x && w) return 0x40; // PAGE_EXECUTE_READWRITE (write implies read) + if (!x && !r && !w) return 0x01; // PAGE_NOACCESS + if (!x && r && !w) return 0x02; // PAGE_READONLY + if (!x && w) return 0x04; // PAGE_READWRITE (write implies read) + + // unreachable + return 0; + } + uint32_t MemoryFlags::to_elf_segment() const { uint32_t flags = 0; if (r) flags |= ElfSegmentFlags::R; diff --git a/src/out/buffer/memory.hpp b/src/asmio/program/memory.hpp similarity index 90% rename from src/out/buffer/memory.hpp rename to src/asmio/program/memory.hpp index 6e98c86..ebc6ca8 100644 --- a/src/out/buffer/memory.hpp +++ b/src/asmio/program/memory.hpp @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include namespace asmio { @@ -34,6 +34,9 @@ namespace asmio { /// Convert to mprotect() flags int to_mprotect() const; + /// Convert to the Windows Memory Protection Constants + uint32_t to_win32() const; + /// Convert to ElfSegmentFlags flags uint32_t to_elf_segment() const; diff --git a/src/out/buffer/segmented.cpp b/src/asmio/program/segmented.cpp similarity index 90% rename from src/out/buffer/segmented.cpp rename to src/asmio/program/segmented.cpp index 5837178..f3bba74 100644 --- a/src/out/buffer/segmented.cpp +++ b/src/asmio/program/segmented.cpp @@ -148,7 +148,7 @@ namespace asmio { buffer.resize(buffer.size() + bytes, value); } - void SegmentedBuffer::insert(uint8_t* data, size_t bytes) { + void SegmentedBuffer::insert(const uint8_t* data, size_t bytes) { auto& buffer = sections[selected].buffer; buffer.insert(buffer.end(), data, data + bytes); } @@ -187,28 +187,6 @@ namespace asmio { return last.start + last.buffer.size() + last.tail; } - void SegmentedBuffer::dump() const { - std::cout << "./unasm.sh " << base_address << " \""; - - for (const BufferSegment& segment : sections) { - std::cout << "SECTION " << (segment.flags.x ? ".text" : ".data") << " \\ndb "; - bool first = true; - - for (uint8_t byte : segment.buffer) { - if (!first) { - std::cout << ", "; - } - - first = false; - std::cout << '0' << std::setfill('0') << std::setw(2) << std::hex << ((int) byte) << "h"; - } - - std::cout << "\\n"; - } - - std::cout << "\\n\"" << std::endl; - } - const std::vector& SegmentedBuffer::segments() const { return sections; } diff --git a/src/out/buffer/segmented.hpp b/src/asmio/program/segmented.hpp similarity index 95% rename from src/out/buffer/segmented.hpp rename to src/asmio/program/segmented.hpp index 5b630cc..8e459d0 100644 --- a/src/out/buffer/segmented.hpp +++ b/src/asmio/program/segmented.hpp @@ -1,10 +1,10 @@ #pragma once -#include -#include -#include +#include +#include +#include +#include -#include "external.hpp" #include "label.hpp" #include "memory.hpp" @@ -158,7 +158,7 @@ namespace asmio { void fill(int64_t bytes, uint8_t value); /// Append arbitrary data into the current section - void insert(uint8_t* data, size_t bytes); + void insert(const uint8_t* data, size_t bytes); /// Select the section to use void use_section(MemoryFlags flags, const std::string& name = ""); @@ -169,9 +169,6 @@ namespace asmio { /// Get the total size in bytes of the whole segmented buffer, can be used only after linking size_t total() const; - /// Print the contests of this buffer for debugging - void dump() const; - /// Get segment list const std::vector& segments() const; diff --git a/src/asmio/program/sizes.hpp b/src/asmio/program/sizes.hpp new file mode 100644 index 0000000..507cec8 --- /dev/null +++ b/src/asmio/program/sizes.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace asmio { + + enum Size : uint8_t { + VOID = 0, + BYTE = 1, // 8 bits + WORD = 2, // 16 bits + DWORD = 4, // 32 bits + QWORD = 8, // 64 bits + TWORD = 10, // 80 bits + XMMWORD = 16, // 128 bits + }; + +} \ No newline at end of file diff --git a/src/out/buffer/writer.cpp b/src/asmio/program/writer.cpp similarity index 95% rename from src/out/buffer/writer.cpp rename to src/asmio/program/writer.cpp index 321f962..ab1568d 100644 --- a/src/out/buffer/writer.cpp +++ b/src/asmio/program/writer.cpp @@ -88,8 +88,8 @@ namespace asmio { buffer.insert((uint8_t*) std::data(dwords), QWORD * dwords.size()); } - void BasicBufferWriter::put_data(size_t bytes, void* date) { - buffer.insert((uint8_t*) date, bytes); + void BasicBufferWriter::put_data(size_t bytes, const void* data) { + buffer.insert((const uint8_t*) data, bytes); } void BasicBufferWriter::put_space(size_t bytes, uint8_t value) { diff --git a/src/out/buffer/writer.hpp b/src/asmio/program/writer.hpp similarity index 95% rename from src/out/buffer/writer.hpp rename to src/asmio/program/writer.hpp index 0b54544..138d9c0 100644 --- a/src/out/buffer/writer.hpp +++ b/src/asmio/program/writer.hpp @@ -1,6 +1,6 @@ #pragma once -#include "external.hpp" +#include #include "label.hpp" #include "segmented.hpp" @@ -42,7 +42,7 @@ namespace asmio { void put_qword(std::initializer_list dword); void put_qword_f(double dword); void put_qword_f(std::initializer_list dword); - void put_data(size_t bytes, void* date); + void put_data(size_t bytes, const void* data); void put_space(size_t bytes, uint8_t value = 0); }; diff --git a/src/util.hpp b/src/asmio/util.hpp similarity index 92% rename from src/util.hpp rename to src/asmio/util.hpp index a74d751..0bec413 100644 --- a/src/util.hpp +++ b/src/asmio/util.hpp @@ -1,9 +1,6 @@ #pragma once -#include -#include -#include -#include +#include template concept trivially_copyable = std::is_trivially_copyable_v; @@ -62,10 +59,32 @@ namespace asmio::util { // https://stackoverflow.com/a/6500499 inline std::string trim(const std::string& str) { - std::string copy = str; - copy.erase(copy.find_last_not_of(' ') + 1); // Suffixing spaces - copy.erase(0, copy.find_first_not_of(' ')); // Prefixing spaces - return copy; + int prefix = 0; // will point to the first non-space char + int suffix = str.length() - 1; + + while (prefix < static_cast(str.length())) { + if (!std::isspace(str[prefix])) { + break; + } + + prefix ++; + } + + while (suffix > 0) { + if (!std::isspace(str[suffix])) { + break; + } + + suffix --; + } + + int count = suffix - prefix + 1; + + if (count < 0) { + count = 0; + } + + return str.substr(prefix, count); } // https://stackoverflow.com/a/46931770 @@ -257,7 +276,7 @@ namespace asmio::util { return std::numeric_limits::max(); } - return (1UL << count) - 1UL; + return (T(1) << count) - T(1); } /** @@ -265,14 +284,16 @@ namespace asmio::util { * to losslessly encode the given signed integer. */ constexpr int min_signed_bytes(int64_t value) { + const uint64_t uval = static_cast(value); + if ((value & 0xFFFF'FFFF'FFFF'FF80) == 0xFFFF'FFFF'FFFF'FF80) return 1; // 1 byte long negative - if (value <= 0x0000'0000'0000'007F) return 1; // 1 byte long positive + if (uval <= 0x0000'0000'0000'007F) return 1; // 1 byte long positive if ((value & 0xFFFF'FFFF'FFFF'8000) == 0xFFFF'FFFF'FFFF'8000) return 2; // 2 byte long negative - if (value <= 0x0000'0000'0000'7FFF) return 2; // 2 byte long positive + if (uval <= 0x0000'0000'0000'7FFF) return 2; // 2 byte long positive if ((value & 0xFFFF'FFFF'8000'0000) == 0xFFFF'FFFF'8000'0000) return 4; // 4 byte long negative - if (value <= 0x0000'0000'7FFF'FFFF) return 4; // 4 byte long positive + if (uval <= 0x0000'0000'7FFF'FFFF) return 4; // 4 byte long positive return 8; } diff --git a/src/out/chunk/buffer.cpp b/src/asmio/util/chunk.cpp similarity index 99% rename from src/out/chunk/buffer.cpp rename to src/asmio/util/chunk.cpp index c134c29..ebe3997 100644 --- a/src/out/chunk/buffer.cpp +++ b/src/asmio/util/chunk.cpp @@ -1,4 +1,4 @@ -#include "buffer.hpp" +#include "chunk.hpp" namespace asmio { diff --git a/src/out/chunk/buffer.hpp b/src/asmio/util/chunk.hpp similarity index 99% rename from src/out/chunk/buffer.hpp rename to src/asmio/util/chunk.hpp index f129927..476064f 100644 --- a/src/out/chunk/buffer.hpp +++ b/src/asmio/util/chunk.hpp @@ -1,8 +1,6 @@ #pragma once -#include -#include -#include "util.hpp" +#include namespace asmio { diff --git a/src/out/chunk/codecs.cpp b/src/asmio/util/codecs.cpp similarity index 75% rename from src/out/chunk/codecs.cpp rename to src/asmio/util/codecs.cpp index 9f4d0fe..92e7d10 100644 --- a/src/out/chunk/codecs.cpp +++ b/src/asmio/util/codecs.cpp @@ -27,7 +27,7 @@ namespace asmio { void SignedLeb128::encode(ChunkBuffer& buffer, int64_t signed_value) { auto value = std::bit_cast(signed_value); - const auto minus_one = std::bit_cast(-1L); + constexpr auto minus_one = static_cast(-1L); const bool negative = signed_value < 0; bool next = true; @@ -35,14 +35,14 @@ namespace asmio { uint8_t byte = value & 0x7F; value >>= 7; - // this is only neccecary if the implementation of >>= + // this is only necessary if the implementation of >>= // uses a logical shift, in practice most C++ implementations - // would (hopefully) use a arythmetic shift when the shifted value is - // of a signed type, however, this behaviour is not well defined and - // relies on undefined behaviour. To avoit it, we explicitly use a - // non-singed type here and implements arythmetic shift ourselves. + // would (hopefully) use an arithmetic shift when the shifted value is + // of a signed type, however, this behavior is not well-defined and + // relies on undefined behavior. To avoid it, we explicitly use a + // non-singed type here and implements arithmetic shift ourselves. if (negative) { - value |= ~0UL << (sizeof(signed_value) * 8 - 7); // sign extend + value |= ~uint64_t(0) << (sizeof(signed_value) * 8 - 7); // sign extend } // sign bit of byte is second high-order bit diff --git a/src/out/chunk/codecs.hpp b/src/asmio/util/codecs.hpp similarity index 97% rename from src/out/chunk/codecs.hpp rename to src/asmio/util/codecs.hpp index bc75519..9e808c5 100644 --- a/src/out/chunk/codecs.hpp +++ b/src/asmio/util/codecs.hpp @@ -1,6 +1,6 @@ #pragma once -#include "buffer.hpp" +#include "chunk.hpp" namespace asmio { diff --git a/src/util/indexer.hpp b/src/asmio/util/indexer.hpp similarity index 99% rename from src/util/indexer.hpp rename to src/asmio/util/indexer.hpp index f735dde..68e1d73 100644 --- a/src/util/indexer.hpp +++ b/src/asmio/util/indexer.hpp @@ -1,4 +1,5 @@ #pragma once + #include template diff --git a/src/asm/util.hpp b/src/asmio/util/macro.hpp similarity index 60% rename from src/asm/util.hpp rename to src/asmio/util/macro.hpp index 13718c7..c20dfa8 100644 --- a/src/asm/util.hpp +++ b/src/asmio/util/macro.hpp @@ -1,7 +1,5 @@ #pragma once -#include - struct StaticBlock { int operator +(const std::function& block) { @@ -11,6 +9,14 @@ struct StaticBlock { }; +#define JOIN_PARTS_RESOLVED(first, second) first##second + +/// Allows one to merge two tokens, even macro values +#define JOIN_PARTS(first, second) JOIN_PARTS_RESOLVED(first, second) + +/// Mark that no padding should be used in marked struct, ever +#define PACKED __attribute__((__packed__)) + /// Used to mark prefixes for the python codegen #define PREFIX BufferWriter& diff --git a/src/asmio/util/platform.cpp b/src/asmio/util/platform.cpp new file mode 100644 index 0000000..42094c1 --- /dev/null +++ b/src/asmio/util/platform.cpp @@ -0,0 +1,341 @@ +#include "platform.hpp" +#include + +#include "tmp.hpp" + +#ifdef __linux__ + +#include +#include +#include +#include +#include + +namespace asmio { + + uint32_t page_size() { + return getpagesize(); + } + + void* allocate_pages(uint64_t bytes, MemoryFlags initial) { + return mmap(nullptr, bytes, initial.to_mprotect(), MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + } + + void protect_pages(void* page, uint64_t bytes, MemoryFlags flags) { + mprotect(page, bytes, flags.to_mprotect()); + } + + void free_pages(void* page, uint64_t bytes) { + munmap(page, bytes); + } + + RunResult run_file_image(const void* image, size_t bytes, const char** argv, const char** envp) { + // verify arguments + if (argv == nullptr || envp == nullptr) { + return RunResult::error(1); + } + + // create in-memory file descriptor + const int memfd = memfd_create("buffer", MFD_ALLOW_SEALING | MFD_CLOEXEC); + if (memfd == -1) { + return RunResult::error(2); + } + + int* flag = (int*) mmap(nullptr, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (flag == nullptr) { + return RunResult::error(3); + } + + // copy buffer into memfd + write(memfd, image, bytes); + + // we use this to check if the child really run or did execve just fail + *flag = 0; + + // add seals to memfd + if (fcntl(memfd, F_ADD_SEALS, F_SEAL_WRITE | F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL) != 0) { + return RunResult::error(4); + } + + const pid_t pid = fork(); + if (pid == -1) { + return RunResult::error(5); + } + + // replace child with memfd elf file + if (pid == 0) { + fexecve(memfd, const_cast(argv), const_cast(envp)); + + // if fexecve fails we need to kill ourselves + *flag = 1; + exit(1); + } + + int status = 0; + + // wait for child and get status code + if (waitpid(pid, &status, 0) == -1) { + return RunResult::error(6); + } + + if (*flag) { + return RunResult::error(7); + } + + free_pages(flag, sizeof(int)); + + // obtain return code from child status + return RunResult::success(WEXITSTATUS(status)); + } + + std::string call_shell(std::string cmd, const std::string& input) { + + // refers to the stdin/stdout of the child process + // [0] read, [1] write + int pipe_stdin[2]; + int pipe_stdout[2]; + + pipe2(pipe_stdin, 0); + pipe2(pipe_stdout, 0); + + pid_t pid = fork(); + + if (pid == 0) { + dup2(pipe_stdin[0], STDIN_FILENO); + dup2(pipe_stdout[1], STDOUT_FILENO); + dup2(pipe_stdout[1], STDERR_FILENO); + + // close the unused end + close(pipe_stdin[1]); + close(pipe_stdout[0]); + + execl("/bin/sh", "sh", "-c", cmd.c_str(), nullptr); + exit(1); + } + + // close the unused end + close(pipe_stdin[0]); + close(pipe_stdout[1]); + + if (!input.empty()) { + write(pipe_stdin[1], input.data(), input.size()); + } + + close(pipe_stdin[1]); + + std::string out; + char buf[256]; + ssize_t n; + + while ((n = read(pipe_stdout[0], buf, sizeof(buf))) > 0) { + out.append(buf, n); + } + + close(pipe_stdout[0]); + waitpid(pid, nullptr, 0); + return out; + } + +} + +#endif + +#ifdef _WIN32 +#include + +namespace asmio { + + uint32_t page_size() { + SYSTEM_INFO info; + GetSystemInfo(&info); + return info.dwPageSize; + } + + void* allocate_pages(uint64_t bytes, MemoryFlags initial) { + return VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, initial.to_win32()); + } + + void protect_pages(void* page, uint64_t bytes, MemoryFlags flags) { + DWORD unused; + VirtualProtect(page, bytes, flags.to_win32(), &unused); + } + + void free_pages(void* page, uint64_t bytes) { + VirtualFree(page, 0, MEM_RELEASE); + } + + RunResult run_file_image(const void* image, size_t bytes, const char** argv, const char** envp) { + + // TODO implement, an actual, in-memory CreateProcess() + // this is done in a non-perfect way, by first copying the image to a file + // there is a way to do this correctly, but it is convoluted + // https://groups.google.com/g/comp.os.ms-windows.programmer.win32/c/Md3GKPc279A/m/Ax3bYgXhpD8J + // https://web.archive.org/web/20131115160730/http://www.security.org.sg/code/loadexe.html + + // TODO don't ignore environ + (void) envp; + + util::TempFile temp; + temp.write((uint8_t*) image, bytes); + + PROCESS_INFORMATION process_info; + STARTUPINFO startup_info; + + ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION)); + ZeroMemory(&startup_info, sizeof(STARTUPINFO)); + + startup_info.cb = sizeof(STARTUPINFO); + + std::string command = temp.path() + " "; + + // start at 1 to ignore name + for (int i = 1; true; i++) { + const char* part = argv[i]; + + if (part == nullptr) { + break; + } + + command += part; + } + + if (!CreateProcess( + nullptr, + (TCHAR*) command.c_str(), + nullptr, + nullptr, + true, // inherit handles + 0, + nullptr, // use parent's environment + nullptr, // use parent's current directory + &startup_info, + &process_info + )) { + return RunResult::error(1); + } + + DWORD exit_code = 1; + + if (WaitForSingleObject(process_info.hProcess, 10000) == WAIT_TIMEOUT) { + return RunResult::error(2); + } + + GetExitCodeProcess(process_info.hProcess, &exit_code); + + CloseHandle(process_info.hProcess); + CloseHandle(process_info.hThread); + + return RunResult::success(exit_code); + + } + + std::string call_shell(std::string cmd, const std::string& input) { + + // https://learn.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output + // https://www.mirabulus.com/it/blog/2021/05/16/hidden-issues-with-inheritable-handles-in-windows + + SECURITY_ATTRIBUTES security_attr; + security_attr.nLength = sizeof(SECURITY_ATTRIBUTES); + security_attr.bInheritHandle = TRUE; + security_attr.lpSecurityDescriptor = nullptr; + + HANDLE child_stdin_read = nullptr; + HANDLE child_stdin_write = nullptr; + HANDLE child_stdout_read = nullptr; + HANDLE child_stdout_write = nullptr; + + CreatePipe(&child_stdout_read, &child_stdout_write, &security_attr, 0); + SetHandleInformation(child_stdout_read, HANDLE_FLAG_INHERIT, 0); + + CreatePipe(&child_stdin_read, &child_stdin_write, &security_attr, 0); + SetHandleInformation(child_stdin_write, HANDLE_FLAG_INHERIT, 0); + + PROCESS_INFORMATION process_info; + STARTUPINFO startup_info; + + ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION)); + ZeroMemory(&startup_info, sizeof(STARTUPINFO)); + + startup_info.cb = sizeof(STARTUPINFO); + startup_info.hStdError = child_stdout_write; + startup_info.hStdOutput = child_stdout_write; + startup_info.hStdInput = child_stdin_read; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + + std::string command = "cmd.exe /C " + cmd; + + if (!CreateProcess( + nullptr, + (TCHAR*) command.c_str(), + nullptr, + nullptr, + true, // inherit handles + CREATE_SUSPENDED, + nullptr, // use parent's environment + nullptr, // use parent's current directory + &startup_info, + &process_info + )) { + throw std::runtime_error {"Failed to create process '" + cmd + "'!"}; + } + + SetHandleInformation(child_stdout_write, HANDLE_FLAG_INHERIT, 0); + SetHandleInformation(child_stdin_read, HANDLE_FLAG_INHERIT, 0); + + // close handles used by the child process + CloseHandle(child_stdout_write); + CloseHandle(child_stdin_read); + + ResumeThread(process_info.hThread); + + const char* data = input.data(); + DWORD bytes = input.size(); + DWORD written = 0; + + while (bytes > 0) { + WriteFile(child_stdin_write, data, bytes, &written, nullptr); + bytes -= written; + } + + CloseHandle(child_stdin_write); + + DWORD exit_code = 1; + std::string output; + + WaitForSingleObject(process_info.hProcess, INFINITE); + GetExitCodeProcess(process_info.hProcess, &exit_code); + + CloseHandle(process_info.hProcess); + CloseHandle(process_info.hThread); + + constexpr DWORD size = 256; + char buffer[size]; + DWORD read = 0; + + while (true) { + bool success = ReadFile(child_stdout_read, buffer, size, &read, nullptr); + + if (!success || (read == 0)) { + break; + } + + output.append(buffer, buffer + read); + } + + CloseHandle(child_stdout_read); + + std::string normalized; + normalized.reserve(output.size()); + + for (char c : output) { + if (c != '\r') { + normalized.push_back(c); + } + } + + return normalized; + } + +} + +#endif \ No newline at end of file diff --git a/src/asmio/util/platform.hpp b/src/asmio/util/platform.hpp new file mode 100644 index 0000000..4f2f4ff --- /dev/null +++ b/src/asmio/util/platform.hpp @@ -0,0 +1,26 @@ +#pragma once +#include + +namespace asmio { + + struct RunResult; + + /// Get size of one page in bytes + uint32_t page_size(); + + /// Allocate memory in pages, in a way that it can be used by protect_pages() + void* allocate_pages(uint64_t bytes, MemoryFlags initial); + + /// Change permissions assigned to memory pages + void protect_pages(void* page, uint64_t bytes, MemoryFlags flags); + + /// Free memory allocated with allocate_pages() + void free_pages(void* page, uint64_t bytes); + + /// Run executable file image + RunResult run_file_image(const void* image, size_t bytes, const char** argv, const char** envp); + + /// Invoke the given command, run it with the given STDIN, and return STDOUT + std::string call_shell(std::string cmd, const std::string& input = ""); + +} diff --git a/src/util/pool.hpp b/src/asmio/util/pool.hpp similarity index 99% rename from src/util/pool.hpp rename to src/asmio/util/pool.hpp index 0b3e0b3..19af802 100644 --- a/src/util/pool.hpp +++ b/src/asmio/util/pool.hpp @@ -1,4 +1,5 @@ #pragma once + #include #include #include diff --git a/src/util/refcnt.hpp b/src/asmio/util/refcnt.hpp similarity index 96% rename from src/util/refcnt.hpp rename to src/asmio/util/refcnt.hpp index 5259b24..485f5c2 100644 --- a/src/util/refcnt.hpp +++ b/src/asmio/util/refcnt.hpp @@ -1,6 +1,6 @@ #pragma once -#include "external.hpp" +#include namespace asmio { diff --git a/src/util/set.hpp b/src/asmio/util/set.hpp similarity index 96% rename from src/util/set.hpp rename to src/asmio/util/set.hpp index 1cd24a4..39fae84 100644 --- a/src/util/set.hpp +++ b/src/asmio/util/set.hpp @@ -1,6 +1,6 @@ #pragma once -#include "external.hpp" +#include namespace asmio::util { diff --git a/src/util/strings.hpp b/src/asmio/util/strings.hpp similarity index 100% rename from src/util/strings.hpp rename to src/asmio/util/strings.hpp diff --git a/src/util/tmp.cpp b/src/asmio/util/tmp.cpp similarity index 78% rename from src/util/tmp.cpp rename to src/asmio/util/tmp.cpp index cc9421c..fe7b98c 100644 --- a/src/util/tmp.cpp +++ b/src/asmio/util/tmp.cpp @@ -1,6 +1,6 @@ #include "tmp.hpp" -#include +#include namespace asmio::util { @@ -25,11 +25,11 @@ namespace asmio::util { } void TempFile::dump() const { - printf("Using temporary file: \"%s\"\n", m_path.c_str()); + printf("Using temporary file: \"%s\"\n", (const char*) m_path.u8string().c_str()); } std::string TempFile::path() const { - return m_path; + return m_path.string(); } } diff --git a/src/util/tmp.hpp b/src/asmio/util/tmp.hpp similarity index 62% rename from src/util/tmp.hpp rename to src/asmio/util/tmp.hpp index bd4a7f2..8a044f3 100644 --- a/src/util/tmp.hpp +++ b/src/asmio/util/tmp.hpp @@ -1,6 +1,6 @@ #pragma once -#include "external.hpp" +#include namespace asmio::util { @@ -23,11 +23,17 @@ namespace asmio::util { } void write(const std::string& content) { - std::ofstream out(path()); + std::fstream out(path(), std::ios::out | std::ios::trunc | std::ios::binary); out << content; out.close(); } + void write(const uint8_t* data, size_t bytes) { + std::fstream out(path(), std::ios::out | std::ios::trunc | std::ios::binary); + out.write((char*) data, bytes); + out.close(); + } + TempFile(const char* extension = ""); ~TempFile(); diff --git a/src/asmio/x86/argument/condition.hpp b/src/asmio/x86/argument/condition.hpp new file mode 100644 index 0000000..87cc2bc --- /dev/null +++ b/src/asmio/x86/argument/condition.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +namespace asmio::x86 { + + enum struct SimdCondition : uint8_t { + EQ = 0, ///< Equal (ordered, non-signaling) + LT = 1, ///< Less-than (ordered, signaling) + LE = 2, ///< Less-than-or-equal (ordered, signaling) + RD = 7, ///< Ordered (ordered, non-signaling) + NEQ = 4, ///< Not-equal (unordered, non-signaling) + NLT = 5, ///< Not-less-than (unordered, signaling) + NLE = 6, ///< Not-greater-than (unordered, signaling) + NRD = 3, ///< Unordered (unordered, non-signaling) + }; + + enum struct Condition : uint8_t { + O = 0x0, ///< Overflow + NO = 0x1, ///< Not Overflow + B = 0x2, C = 0x2, NAE = 0x2, ///< Below, Carry, Not Above or Equal + NB = 0x3, NC = 0x3, AE = 0x3, ///< Not Below, Not Carry, Above or Equal + E = 0x4, Z = 0x4, ///< Equal, Zero + NE = 0x5, NZ = 0x5, ///< Not Equal, Not Zero + BE = 0x6, NA = 0x6, ///< Below or Equal, Not Above + NBE = 0x7, A = 0x7, ///< Not Below or Equal, Above + S = 0x8, ///< Sign + NS = 0x9, ///< Not Sign + P = 0xA, PE = 0xA, ///< Parity, Parity Even + NP = 0xB, PO = 0xB, ///< Not Parity, Parity Odd + L = 0xC, NGE = 0xC, ///< Less, Not Greater or Equal + NL = 0xD, GE = 0xD, ///< Not Less, Greater or Equal + LE = 0xE, NG = 0xE, ///< Less or Equal, Not Greater + NLE = 0xf, G = 0xF, ///< Not Less or Equal, Greater + }; + +} \ No newline at end of file diff --git a/src/asm/x86/argument/location.cpp b/src/asmio/x86/argument/location.cpp similarity index 99% rename from src/asm/x86/argument/location.cpp rename to src/asmio/x86/argument/location.cpp index 3a3b0bf..ff58d68 100644 --- a/src/asm/x86/argument/location.cpp +++ b/src/asmio/x86/argument/location.cpp @@ -1,4 +1,3 @@ - #include "location.hpp" namespace asmio::x86 { diff --git a/src/asm/x86/argument/location.hpp b/src/asmio/x86/argument/location.hpp similarity index 91% rename from src/asm/x86/argument/location.hpp rename to src/asmio/x86/argument/location.hpp index 386fd1c..ac4fbac 100644 --- a/src/asm/x86/argument/location.hpp +++ b/src/asmio/x86/argument/location.hpp @@ -1,11 +1,12 @@ #pragma once -#include "external.hpp" -#include "out/buffer/sizes.hpp" +#include +#include +#include +#include + #include "registry.hpp" #include "scaled.hpp" -#include "out/buffer/label.hpp" -#include "../../util.hpp" namespace asmio::x86 { @@ -49,6 +50,14 @@ namespace asmio::x86 { public: Location ref() const { + if (base != UNSET && !base.is(Registry::GENERAL)) { + throw std::runtime_error {"Can't dereference a non-general purpose base register"}; + } + + if (index != UNSET && !index.is(Registry::GENERAL)) { + throw std::runtime_error {"Can't dereference a non-general purpose index register"}; + } + check_non_referential("Can't reference a reference!"); return Location {base, index, scale, offset, label, VOID, true}; } @@ -92,8 +101,8 @@ namespace asmio::x86 { } /// Checks if this location is a simple un-referenced register - constexpr bool is_simple() const { - return base.is(Registry::GENERAL) && !is_indexed() && offset == 0 && !reference && !is_labeled(); + constexpr bool is_simple(Registry::Flag mask = Registry::STANDARD) const { + return base.is(mask) && !is_indexed() && offset == 0 && !reference && !is_labeled(); } /// Checks if this location is a simple un-referenced accumulator, used when encoding short-forms diff --git a/src/asm/x86/argument/registry.hpp b/src/asmio/x86/argument/registry.hpp similarity index 84% rename from src/asm/x86/argument/registry.hpp rename to src/asmio/x86/argument/registry.hpp index 068009b..141a762 100644 --- a/src/asm/x86/argument/registry.hpp +++ b/src/asmio/x86/argument/registry.hpp @@ -1,10 +1,9 @@ #pragma once -#include "external.hpp" -#include "asm/x86/const.hpp" -#include "out/buffer/sizes.hpp" -#include "asm/util.hpp" -#include +#include +#include +#include +#include namespace asmio::x86 { @@ -44,10 +43,11 @@ namespace asmio::x86 { NONE = 0b0000000, GENERAL = 0b0000001, - FLOATING = 0b0000010, + STANDARD = 0b0111111, ACCUMULATOR = 0b0000100, // this is the accumulator (RAX/EAX/AX) REX = 0b0001000, // this registers requires the REX prefix to be encoded HIGH_BYTE = 0b0010000, // registers that CANT be used with REX prefix present + XMM = 0b0100000, // registers used in the SSE x86 extension }; const uint8_t size; // size in bytes @@ -123,7 +123,7 @@ namespace asmio::x86 { constexpr Registry BP {WORD, 0b0101, Registry::GENERAL}; constexpr Registry ESP {DWORD, 0b0100, Registry::GENERAL}; constexpr Registry SP {WORD, 0b0100, Registry::GENERAL}; - constexpr Registry ST {TWORD, 0b0000, Registry::FLOATING}; + constexpr Registry ST {TWORD, 0b0000, Registry::NONE}; /* * Amd64 surrogates - uniform byte registers @@ -179,4 +179,25 @@ namespace asmio::x86 { constexpr Registry R15D {DWORD, 0b1111, Registry::GENERAL | Registry::REX}; constexpr Registry R15 {QWORD, 0b1111, Registry::GENERAL | Registry::REX}; + /* + * SSE + */ + + constexpr Registry XMM0 {XMMWORD, 0b0000, Registry::XMM}; + constexpr Registry XMM1 {XMMWORD, 0b0001, Registry::XMM}; + constexpr Registry XMM2 {XMMWORD, 0b0010, Registry::XMM}; + constexpr Registry XMM3 {XMMWORD, 0b0011, Registry::XMM}; + constexpr Registry XMM4 {XMMWORD, 0b0100, Registry::XMM}; + constexpr Registry XMM5 {XMMWORD, 0b0101, Registry::XMM}; + constexpr Registry XMM6 {XMMWORD, 0b0110, Registry::XMM}; + constexpr Registry XMM7 {XMMWORD, 0b0111, Registry::XMM}; + constexpr Registry XMM8 {XMMWORD, 0b1000, Registry::XMM | Registry::REX}; + constexpr Registry XMM9 {XMMWORD, 0b1001, Registry::XMM | Registry::REX}; + constexpr Registry XMM10 {XMMWORD, 0b1010, Registry::XMM | Registry::REX}; + constexpr Registry XMM11 {XMMWORD, 0b1011, Registry::XMM | Registry::REX}; + constexpr Registry XMM12 {XMMWORD, 0b1100, Registry::XMM | Registry::REX}; + constexpr Registry XMM13 {XMMWORD, 0b1101, Registry::XMM | Registry::REX}; + constexpr Registry XMM14 {XMMWORD, 0b1110, Registry::XMM | Registry::REX}; + constexpr Registry XMM15 {XMMWORD, 0b1111, Registry::XMM | Registry::REX}; + } \ No newline at end of file diff --git a/src/asm/x86/argument/scaled.hpp b/src/asmio/x86/argument/scaled.hpp similarity index 95% rename from src/asm/x86/argument/scaled.hpp rename to src/asmio/x86/argument/scaled.hpp index b26560b..061fc74 100644 --- a/src/asm/x86/argument/scaled.hpp +++ b/src/asmio/x86/argument/scaled.hpp @@ -1,8 +1,9 @@ #pragma once -#include "external.hpp" +#include +#include + #include "registry.hpp" -#include "asm/x86/const.hpp" namespace asmio::x86 { diff --git a/src/asm/x86/const.hpp b/src/asmio/x86/const.hpp similarity index 98% rename from src/asm/x86/const.hpp rename to src/asmio/x86/const.hpp index c0c20f7..7ca16e5 100644 --- a/src/asm/x86/const.hpp +++ b/src/asmio/x86/const.hpp @@ -1,6 +1,6 @@ #pragma once -#include "external.hpp" +#include namespace asmio::x86 { diff --git a/src/asm/x86/module.cpp b/src/asmio/x86/module.cpp similarity index 69% rename from src/asm/x86/module.cpp rename to src/asmio/x86/module.cpp index a6e082c..d7de809 100644 --- a/src/asm/x86/module.cpp +++ b/src/asmio/x86/module.cpp @@ -1,7 +1,7 @@ #include "module.hpp" #include "writer.hpp" -#include "src/tasml/stream.hpp" +#include namespace asmio::x86 { @@ -17,6 +17,7 @@ namespace asmio::x86 { {"dword", DWORD}, {"qword", QWORD}, {"tword", TWORD}, + {"xmmword", XMMWORD}, {"float", DWORD}, {"double", QWORD}, @@ -106,6 +107,24 @@ namespace asmio::x86 { if (raw == "r15d") return R15D; if (raw == "r15") return R15; + // sse + if (raw == "xmm0") return XMM0; + if (raw == "xmm1") return XMM1; + if (raw == "xmm2") return XMM2; + if (raw == "xmm3") return XMM3; + if (raw == "xmm4") return XMM4; + if (raw == "xmm5") return XMM5; + if (raw == "xmm6") return XMM6; + if (raw == "xmm7") return XMM7; + if (raw == "xmm8") return XMM8; + if (raw == "xmm9") return XMM9; + if (raw == "xmm10") return XMM10; + if (raw == "xmm11") return XMM11; + if (raw == "xmm12") return XMM12; + if (raw == "xmm13") return XMM13; + if (raw == "xmm14") return XMM14; + if (raw == "xmm15") return XMM15; + throw std::runtime_error {"Unknown registry " + token->quoted()}; } @@ -114,7 +133,7 @@ namespace asmio::x86 { const Token* index = nullptr; const Token* scale = nullptr; const Token* label = nullptr; - long offset = 0; + int64_t offset = 0; // the next token MUST be this, else parse throws const char* expect = nullptr; @@ -250,7 +269,22 @@ namespace asmio::x86 { return parse_expression(stream); } - static Location parse_location(TokenStream stream) { + template + T parse_argument(TokenStream stream); + + template + T parse_argument(TokenStream stream) { + return stream.expect(Token::INT).as_int(); + } + + template <> + Registry parse_argument(TokenStream stream) { + const Token& name = stream.expect(Token::NAME); + return token_to_register(&name); + } + + template <> + Location parse_argument(TokenStream stream) { const Token* name = &stream.peek(); const int size = token_to_sizing(name); @@ -262,10 +296,60 @@ namespace asmio::x86 { return parse_inner(stream); } - template - Location parse_argument(TokenStream stream) { - static_assert(std::is_same_v, "x86 can only accept Location classes as arguments"); - return parse_location(stream); + template <> + SimdCondition parse_argument(TokenStream stream) { + const Token& token = stream.expect(Token::NAME); + std::string raw = util::to_lower(token.raw); + + if (raw == "eq") return SimdCondition::EQ; + if (raw == "lt") return SimdCondition::LT; + if (raw == "le") return SimdCondition::LE; + if (raw == "rd") return SimdCondition::RD; + if (raw == "neq") return SimdCondition::NEQ; + if (raw == "nlt") return SimdCondition::NLT; + if (raw == "nle") return SimdCondition::NLE; + if (raw == "nrd") return SimdCondition::NRD; + + throw std::runtime_error {"Unknown SIMD condition '" + raw + "'"}; + } + + template <> + Condition parse_argument(TokenStream stream) { + const Token& token = stream.expect(Token::NAME); + std::string raw = util::to_lower(token.raw); + + if (raw == "o") return Condition::O; + if (raw == "no") return Condition::NO; + if (raw == "b") return Condition::B; + if (raw == "c") return Condition::C; + if (raw == "nae") return Condition::NAE; + if (raw == "nb") return Condition::NB; + if (raw == "nc") return Condition::NC; + if (raw == "ae") return Condition::AE; + if (raw == "e") return Condition::E; + if (raw == "z") return Condition::Z; + if (raw == "ne") return Condition::NE; + if (raw == "nz") return Condition::NZ; + if (raw == "be") return Condition::BE; + if (raw == "na") return Condition::NA; + if (raw == "nbe") return Condition::NBE; + if (raw == "a") return Condition::A; + if (raw == "s") return Condition::S; + if (raw == "ns") return Condition::NS; + if (raw == "p") return Condition::P; + if (raw == "pe") return Condition::PE; + if (raw == "np") return Condition::NP; + if (raw == "po") return Condition::PO; + if (raw == "l") return Condition::L; + if (raw == "nge") return Condition::NGE; + if (raw == "nl") return Condition::NL; + if (raw == "ge") return Condition::GE; + if (raw == "le") return Condition::LE; + if (raw == "ng") return Condition::NG; + if (raw == "nle") return Condition::NLE; + if (raw == "g") return Condition::G; + + throw std::runtime_error {"Unknown condition '" + raw + "'"}; } # include "generated/x86.hpp" diff --git a/src/asm/x86/module.hpp b/src/asmio/x86/module.hpp similarity index 86% rename from src/asm/x86/module.hpp rename to src/asmio/x86/module.hpp index 51fe2e0..81c5126 100644 --- a/src/asm/x86/module.hpp +++ b/src/asmio/x86/module.hpp @@ -1,6 +1,7 @@ #pragma once -#include -#include + +#include +#include namespace asmio::x86 { diff --git a/src/asm/x86/writer.cpp b/src/asmio/x86/writer.cpp similarity index 97% rename from src/asm/x86/writer.cpp rename to src/asmio/x86/writer.cpp index 3e99d4c..6f3f834 100644 --- a/src/asm/x86/writer.cpp +++ b/src/asmio/x86/writer.cpp @@ -1,9 +1,6 @@ #include "writer.hpp" -// private libs -#include - -#include "out/buffer/linkage.hpp" +#include namespace asmio::x86 { @@ -524,7 +521,7 @@ namespace asmio::x86 { long addend = dst.offset; if (!dst.is_jump_label()) { - throw std::runtime_error {"Invalid operand"}; + throw std::runtime_error {"Invalid operand, expected label"}; } if (buffer.has_label(label)) { @@ -552,13 +549,6 @@ namespace asmio::x86 { } - /** - * Used for constructing the 'set byte' family of instructions - */ - void BufferWriter::put_inst_setx(const Location& dst, uint8_t lopcode) { - put_inst_std_as(0b1001'0000 | lopcode, dst, RegInfo::raw(0), true); - } - void BufferWriter::put_rex_w() { put_byte(REX_PREFIX | REX_BIT_W); } diff --git a/src/asm/x86/writer.hpp b/src/asmio/x86/writer.hpp similarity index 76% rename from src/asm/x86/writer.hpp rename to src/asmio/x86/writer.hpp index a16f250..d4f81bf 100644 --- a/src/asm/x86/writer.hpp +++ b/src/asmio/x86/writer.hpp @@ -1,10 +1,11 @@ #pragma once -#include "external.hpp" +#include +#include +#include + +#include "argument/condition.hpp" #include "argument/location.hpp" -#include "out/buffer/segmented.hpp" -#include "../util.hpp" -#include "out/buffer/writer.hpp" namespace asmio::x86 { @@ -62,8 +63,12 @@ namespace asmio::x86 { /// Used for constructing the conditional jump family of instructions void put_inst_jx(const Location& label, uint8_t sopcode, uint8_t lopcode); - /// Used for constructing the 'set byte' family of instructions - void put_inst_setx(const Location& dst, uint8_t lopcode); + /// Put SSE double XMM register operand instruction + void put_inst_sse_2xmm(uint8_t opcode, Registry dst, Registry src); + void put_inst_sse(uint8_t opcode, Registry reg, const Location& loc); + void put_inst_sse_sized(uint8_t opcode, Registry reg, const Location& loc, uint8_t size, bool prefix = true); + void put_inst_mxcsr(const Location& loc, uint8_t opcode); + void put_inst_movxps(Location dst, Location src, uint8_t opcode); /// Add the REX.W prefix void put_rex_w(); @@ -154,6 +159,7 @@ namespace asmio::x86 { INST put_shrd(Location dst, Location src, Location cnt); ///< Double Right Shift INST put_jmp(Location dst); ///< Unconditional Jump INST put_call(Location dst); ///< Procedure Call + INST put_j(Location label, Condition cond); ///< Jump on Condition INST put_jo(Location label); ///< Jump on Overflow INST put_jno(Location label); ///< Jump on Not Overflow INST put_jb(Location label); ///< Jump on Below @@ -191,6 +197,7 @@ namespace asmio::x86 { INST put_loopz(Location label); ///< Loop RCX Times, if zero INST put_loopne(Location label); ///< Loop RCX Times, if not equal INST put_loopnz(Location label); ///< Loop RCX Times, if not zero + INST put_set(Location dst, Condition cond); /// Set Byte on Condition INST put_seto(Location dst); ///< Set Byte on Overflow INST put_setno(Location dst); ///< Set Byte on Not Overflow INST put_setb(Location dst); ///< Set Byte on Below @@ -207,28 +214,28 @@ namespace asmio::x86 { INST put_setnl(Location dst); ///< Set Byte on Not Less INST put_setle(Location dst); ///< Set Byte on Less or Equal INST put_setnle(Location dst); ///< Set Byte on Not Less or Equal - INST put_setc(Location dst); ///< Alias to JB, Jump on Carry - INST put_setnc(Location dst); ///< Alias to JNB, Jump on Not Carry - INST put_setnae(Location dst); ///< Alias to JB, Jump on Not Above or Equal - INST put_setae(Location dst); ///< Alias to JNB, Jump on Above or Equal - INST put_setz(Location dst); ///< Alias to JE, Jump on Zero - INST put_setnz(Location dst); ///< Alias to JNE, Jump on Not Zero - INST put_setna(Location dst); ///< Alias to JBE, Jump on Not Above - INST put_seta(Location dst); ///< Alias to JNBE, Jump on Above - INST put_setpe(Location dst); ///< Alias to JP, Jump on Parity Even - INST put_setpo(Location dst); ///< Alias to JNP, Jump on Parity Odd - INST put_setnge(Location dst); ///< Alias to JL, Jump on Not Greater or Equal - INST put_setge(Location dst); ///< Alias to JNL, Jump on Greater or Equal - INST put_setng(Location dst); ///< Alias to JLE, Jump on Not Greater - INST put_setg(Location dst); ///< Alias to JNLE, Jump on Greater - INST put_int(Location type); ///< Interrupt + INST put_setc(Location dst); ///< Alias to SETB, Set Byte on Carry + INST put_setnc(Location dst); ///< Alias to SETNB, Set Byte on Not Carry + INST put_setnae(Location dst); ///< Alias to SETB, Set Byte on Not Above or Equal + INST put_setae(Location dst); ///< Alias to SETNB, Set Byte on Above or Equal + INST put_setz(Location dst); ///< Alias to SETE, Set Byte on Zero + INST put_setnz(Location dst); ///< Alias to SETNE, Set Byte on Not Zero + INST put_setna(Location dst); ///< Alias to SETBE, Set Byte on Not Above + INST put_seta(Location dst); ///< Alias to SETNBE, Set Byte on Above + INST put_setpe(Location dst); ///< Alias to SETP, Set Byte on Parity Even + INST put_setpo(Location dst); ///< Alias to SETNP, Set Byte on Parity Odd + INST put_setnge(Location dst); ///< Alias to SETL, Set Byte on Not Greater or Equal + INST put_setge(Location dst); ///< Alias to SETNL, Set Byte on Greater or Equal + INST put_setng(Location dst); ///< Alias to SETLE, Set Byte on Not Greater + INST put_setg(Location dst); ///< Alias to SETNLE, Set Byte on Greater + INST put_int(uint8_t offset); ///< Interrupt INST put_into(); ///< Interrupt if Overflow INST put_iret(); ///< Return from Interrupt INST put_nop(); ///< No Operation INST put_hlt(); ///< Halt INST put_wait(); ///< Wait INST put_ud2(); ///< Undefined Instruction - INST put_enter(Location alc, Location nst); ///< Enter Procedure + INST put_enter(uint16_t alc, uint8_t nst); ///< Enter Procedure INST put_leave(); ///< Leave Procedure INST put_pusha(); ///< Push RBX, RBP, R12-R15 INST put_popa(); ///< Pop RBX, RBP, R12-R15 @@ -243,9 +250,9 @@ namespace asmio::x86 { INST put_std(); ///< Set Direction Flag INST put_cli(); ///< Clear Interrupt Flag INST put_sti(); ///< Set Interrupt Flag - INST put_scf(Location src); ///< Set Carry Flag to Immediate, ASMIOV extension - INST put_sdf(Location src); ///< Set Direction Flag to Immediate, ASMIOV extension - INST put_sif(Location src); ///< Set Interrupt Flag to Immediate, ASMIOV extension + INST put_scf(bool src); ///< Set Carry Flag to Immediate, ASMIOV extension + INST put_sdf(bool src); ///< Set Direction Flag to Immediate, ASMIOV extension + INST put_sif(bool src); ///< Set Interrupt Flag to Immediate, ASMIOV extension INST put_sahf(); ///< Store AH into flags INST put_lahf(); ///< Load status flags into AH register INST put_aaa(); ///< ASCII adjust for add @@ -258,16 +265,17 @@ namespace asmio::x86 { INST put_in(Location dst, Location src); ///< Input from Port INST put_out(Location dst, Location src); ///< Output to Port INST put_test(Location dst, Location src); ///< Test For Bit Pattern - INST put_test(Location src); ///< Sets flags accordingly to the value of register given, ASMIOV extension + INST put_test(Registry src); ///< Sets flags accordingly to the value of register given, ASMIOV extension INST put_ret(); ///< Return from procedure - INST put_ret(Location bytes); ///< Return from procedure and pop X bytes + INST put_ret(uint16_t offset); ///< Return from procedure and pop X bytes + INST put_cpuid(); ///< Return CPU information in EAX, EBX, ECX, and EDX registers // i486 - INST put_xadd(Location dst, Location src); ///< Exchange and Add + INST put_xadd(Location dst, Registry src); ///< Exchange and Add INST put_bswap(Location dst); ///< Byte Swap INST put_invd(); ///< Invalidate Internal Caches INST put_wbinvd(); ///< Write Back and Invalidate Cache - INST put_cmpxchg(Location dst, Location src); ///< Compare and Exchange + INST put_cmpxchg(Location dst, Registry src); ///< Compare and Exchange // x86-64 INST put_cqo(); ///< Convert Doubleword to Quadword @@ -353,6 +361,52 @@ namespace asmio::x86 { INST put_fdivr(Location dst, Location src); ///< Reverse Divide INST put_fdivrp(Location dst); ///< Reverse Divide And Pop + // sse + INST put_movaps(Location dst, Location src); ///< Move Aligned Packed f32 Values + INST put_movhlps(Registry dst, Registry src); ///< Move Packed f32 Values High to Low + INST put_movlhps(Registry dst, Registry src); ///< Move Packed f32 Values Low to High + INST put_movhps(Location dst, Location src); ///< Move two packed f32 values from m64 to high quadword of dst + INST put_movlps(Location dst, Location src); ///< Move two packed f32 values from m64 to low quadword of dst + INST put_movmskps(Registry dst, Registry src); ///< Extract Packed f32 Sign Mask + INST put_movss(Location dst, Location src); ///< Move or Merge Scalar f32 Value + INST put_movups(Location dst, Location src); ///< Move Unaligned Packed f32 Values + INST put_addps(Registry dst, Location src); ///< Add Packed f32 values + INST put_addss(Registry dst, Location src); ///< Add Scalar f32 Values + INST put_divps(Registry dst, Location src); ///< Divide Packed f32 values + INST put_divss(Registry dst, Location src); ///< Divide Scalar f32 values + INST put_maxps(Registry dst, Location src); ///< Compute Maximum of packed f32 values + INST put_maxss(Registry dst, Location src); ///< Compute Maximum of Scalar f32 values + INST put_minps(Registry dst, Location src); ///< Compute Minimum of packed f32 values + INST put_minss(Registry dst, Location src); ///< Compute Minimum of Scalar f32 values + INST put_mulps(Registry dst, Location src); ///< Multiply Packed f32 Values + INST put_mulss(Registry dst, Location src); ///< Multiply Scalar f32 Values + INST put_rcpps(Registry dst, Location src); ///< Compute Reciprocals of Packed f32 values + INST put_rcpss(Registry dst, Location src); ///< Compute Reciprocals of Scalar f32 values + INST put_rsqrtps(Registry dst, Location src); ///< Compute Square Root Reciprocals of Packed f32 values + INST put_rsqrtss(Registry dst, Location src); ///< Compute Square Root Reciprocals of Scalar f32 values + INST put_sqrtps(Registry dst, Location src); ///< Compute Square Roots of Packed f32 values + INST put_sqrtss(Registry dst, Location src); ///< Compute Square Roots of Scalar f32 values + INST put_subps(Registry dst, Location src); ///< Subtract Packed f32 values + INST put_subss(Registry dst, Location src); ///< Subtract Scalar f32 values + INST put_cmpps(Registry dst, Location src, SimdCondition cond); ///< Compare Packed f32 values + INST put_cmpss(Registry dst, Location src, SimdCondition cond); ///< Compare Scalar f32 values + INST put_comiss(Registry dst, Location src); ///< Compare Scalar Ordered f32 Values and Set EFLAGS + INST put_ucomiss(Registry dst, Location src); ///< Compare Scalar Unordered f32 Values and Set EFLAGS + INST put_andnps(Registry dst, Location src); ///< Bitwise logical AND NOT of packed dword values + INST put_andps(Registry dst, Location src); ///< Bitwise logical AND of packed dword values + INST put_orps(Registry dst, Location src); ///< Bitwise logical OR of packed dword values + INST put_xorps(Registry dst, Location src); ///< Bitwise logical XOR of packed dword values + INST put_shufps(Registry dst, Location src, uint8_t selector); ///< Packed Interleave Shuffle of Quadruplets of f32 Values + INST put_unpckhps(Registry dst, Location src); ///< Unpack and Interleave High Packed f32 Values + INST put_unpcklps(Registry dst, Location src); ///< Unpack and Interleave Low Packed f32 Values + INST put_cvtsi2ss(Registry dst, Location src); ///< Convert Doubleword Integer to Scalar f32 Value + INST put_cvtss2si(Registry dst, Location src); ///< Convert Scalar f32 Value to Doubleword Integer + INST put_cvttss2si(Registry dst, Location src); ///< Convert With Truncation Scalar f32 Value to Integer + INST put_ldmxcsr(Location src); ///< Load MXCSR Register from src + INST put_stmxcsr(Location dst); ///< Store MXCSR Register into dst + INST put_movntps(Location dst, Registry src); ///< Store Packed f32 Values Using Non-Temporal Hint + INST put_sfence(); ///< Store Fence + }; } diff --git a/src/asm/x86/instructions/cpu.cpp b/src/asmio/x86/writer_cpu.cpp similarity index 90% rename from src/asm/x86/instructions/cpu.cpp rename to src/asmio/x86/writer_cpu.cpp index 02d9499..9c7d962 100644 --- a/src/asm/x86/instructions/cpu.cpp +++ b/src/asmio/x86/writer_cpu.cpp @@ -1,4 +1,4 @@ -#include "asm/x86/writer.hpp" +#include "writer.hpp" namespace asmio::x86 { @@ -502,11 +502,25 @@ namespace asmio::x86 { } if (dst.is_simple() && src.is_memreg() && val.is_immediate()) { - put_inst_std_dw(0b011010, src, dst.base.pack(), pair_size(src, dst), true /* TODO: sign flag */, true); + const uint64_t imm = val.offset; + const uint8_t bytes = util::min_signed_bytes(imm); + const uint8_t size = pair_size(src, dst); - // not sure why but it looks like IMUL uses 8bit immediate values - put_byte(val.offset); - return; + if (bytes == BYTE) { + put_inst_std(0x6B, src, dst.base.pack(), size); + put_byte(val.offset); + return; + } + + const uint8_t limit = std::min(static_cast(DWORD), size); + + if (bytes <= limit) { + put_inst_std(0x69, src, dst.base.pack(), size); + put_data(limit, &val.offset); + return; + } + + throw std::runtime_error {"Invalid immediate operand, value too long"}; } throw std::runtime_error {"Invalid operands"}; @@ -729,182 +743,189 @@ namespace asmio::x86 { throw std::runtime_error {"Invalid operand"}; } + ///< Jump on Condition + void BufferWriter::put_j(Location label, Condition cond) { + const auto code = static_cast(cond); + put_inst_jx(label, 0b01110000 | code, 0b10000000 | code); + } + /// Jump on Overflow void BufferWriter::put_jo(Location label) { - put_inst_jx(label, 0b01110000, 0b10000000); + put_j(label, Condition::O); } /// Jump on Not Overflow void BufferWriter::put_jno(Location label) { - put_inst_jx(label, 0b01110001, 0b10000001); + put_j(label, Condition::NO); } /// Jump on Below void BufferWriter::put_jb(Location label) { - put_inst_jx(label, 0b01110010, 0b10000010); + put_j(label, Condition::B); } /// Jump on Not Below void BufferWriter::put_jnb(Location label) { - put_inst_jx(label, 0b01110011, 0b10000011); + put_j(label, Condition::NB); } /// Jump on Equal void BufferWriter::put_je(Location label) { - put_inst_jx(label, 0b01110100, 0b10000100); + put_j(label, Condition::E); } /// Jump on Not Equal void BufferWriter::put_jne(Location label) { - put_inst_jx(label, 0b01110101, 0b10000101); + put_j(label, Condition::NE); } /// Jump on Below or Equal void BufferWriter::put_jbe(Location label) { - put_inst_jx(label, 0b01110110, 0b10000110); + put_j(label, Condition::BE); } /// Jump on Not Below or Equal void BufferWriter::put_jnbe(Location label) { - put_inst_jx(label, 0b01110111, 0b10000111); + put_j(label, Condition::NBE); } /// Jump on Sign void BufferWriter::put_js(Location label) { - put_inst_jx(label, 0b01111000, 0b10001000); + put_j(label, Condition::S); } /// Jump on Not Sign void BufferWriter::put_jns(Location label) { - put_inst_jx(label, 0b01111001, 0b10001001); + put_j(label, Condition::NS); } /// Jump on Parity void BufferWriter::put_jp(Location label) { - put_inst_jx(label, 0b01111010, 0b10001010); + put_j(label, Condition::P); } /// Jump on Not Parity void BufferWriter::put_jnp(Location label) { - put_inst_jx(label, 0b01111011, 0b10001011); + put_j(label, Condition::NP); } /// Jump on Less void BufferWriter::put_jl(Location label) { - put_inst_jx(label, 0b01111100, 0b10001100); + put_j(label, Condition::L); } /// Jump on Not Less void BufferWriter::put_jnl(Location label) { - put_inst_jx(label, 0b01111101, 0b10001101); + put_j(label, Condition::NL); } /// Jump on Less or Equal void BufferWriter::put_jle(Location label) { - put_inst_jx(label, 0b01111110, 0b10001110); + put_j(label, Condition::LE); } /// Jump on Not Less or Equal void BufferWriter::put_jnle(Location label) { - put_inst_jx(label, 0b01111111, 0b10001111); + put_j(label, Condition::NLE); + } + + /// Set Byte on Condition + void BufferWriter::put_set(Location dst, Condition cond) { + put_inst_std_as(0b1001'0000 | static_cast(cond), dst, RegInfo::raw(0), true); } /// Set Byte on Overflow void BufferWriter::put_seto(Location dst) { - put_inst_setx(dst, 0); + put_set(dst, Condition::O); } /// Set Byte on Not Overflow void BufferWriter::put_setno(Location dst) { - put_inst_setx(dst, 1); + put_set(dst, Condition::NO); } /// Set Byte on Below void BufferWriter::put_setb(Location dst) { - put_inst_setx(dst, 2); + put_set(dst, Condition::B); } /// Set Byte on Not Below void BufferWriter::put_setnb(Location dst) { - put_inst_setx(dst, 3); + put_set(dst, Condition::NB); } /// Set Byte on Equal void BufferWriter::put_sete(Location dst) { - put_inst_setx(dst, 4); + put_set(dst, Condition::E); } /// Set Byte on Not Equal void BufferWriter::put_setne(Location dst) { - put_inst_setx(dst, 5); + put_set(dst, Condition::NE); } /// Set Byte on Below or Equal void BufferWriter::put_setbe(Location dst) { - put_inst_setx(dst, 6); + put_set(dst, Condition::BE); } /// Set Byte on Not Below or Equal void BufferWriter::put_setnbe(Location dst) { - put_inst_setx(dst, 7); + put_set(dst, Condition::NBE); } /// Set Byte on Sign void BufferWriter::put_sets(Location dst) { - put_inst_setx(dst, 8); + put_set(dst, Condition::S); } /// Set Byte on Not Sign void BufferWriter::put_setns(Location dst) { - put_inst_setx(dst, 9); + put_set(dst, Condition::NS); } /// Set Byte on Parity void BufferWriter::put_setp(Location dst) { - put_inst_setx(dst, 10); + put_set(dst, Condition::P); } /// Set Byte on Not Parity void BufferWriter::put_setnp(Location dst) { - put_inst_setx(dst, 11); + put_set(dst, Condition::NP); } /// Set Byte on Less void BufferWriter::put_setl(Location dst) { - put_inst_setx(dst, 12); + put_set(dst, Condition::L); } /// Set Byte on Not Less void BufferWriter::put_setnl(Location dst) { - put_inst_setx(dst, 13); + put_set(dst, Condition::NL); } /// Set Byte on Less or Equal void BufferWriter::put_setle(Location dst) { - put_inst_setx(dst, 14); + put_set(dst, Condition::LE); } /// Set Byte on Not Less or Equal void BufferWriter::put_setnle(Location dst) { - put_inst_setx(dst, 15); + put_set(dst, Condition::NLE); } /// Interrupt - void BufferWriter::put_int(Location type) { - - if (!type.is_immediate()) { - throw std::runtime_error {"Invalid operand"}; - } + void BufferWriter::put_int(uint8_t offset) { // short form - if (type.offset == 3) { + if (offset == 3) { put_byte(0xCC); return; } // standard form put_byte(0b11001101); - put_byte(type.offset); + put_byte(offset); } @@ -940,15 +961,10 @@ namespace asmio::x86 { } /// Enter Procedure - void BufferWriter::put_enter(Location alc, Location nst) { - if (alc.is_immediate() && nst.is_immediate()) { - put_byte(0b11001000); - put_word(alc.offset); - put_byte(nst.offset); - return; - } - - throw std::runtime_error {"Invalid operands, immediate value expected"}; + void BufferWriter::put_enter(uint16_t alc, uint8_t nst) { + put_byte(0b11001000); + put_word(alc); + put_byte(nst); } /// Leave Procedure @@ -1034,30 +1050,18 @@ namespace asmio::x86 { } /// Set Interrupt Flag to Immediate, ASMIOV extension - void BufferWriter::put_sif(Location src) { - if (!src.is_immediate()) { - throw std::runtime_error {"Invalid operand"}; - } - - if (src.offset == 0) put_cli(); else put_sti(); + void BufferWriter::put_sif(bool src) { + if (src == false) put_cli(); else put_sti(); } /// Set Carry Flag to Immediate, ASMIOV extension - void BufferWriter::put_scf(Location src) { - if (!src.is_immediate()) { - throw std::runtime_error {"Invalid operand"}; - } - - if (src.offset == 0) put_clc(); else put_stc(); + void BufferWriter::put_scf(bool src) { + if (src == false) put_clc(); else put_stc(); } /// Set Direction Flag to Immediate, ASMIOV extension - void BufferWriter::put_sdf(Location src) { - if (!src.is_immediate()) { - throw std::runtime_error {"Invalid operand"}; - } - - if (src.offset == 0) put_cld(); else put_std(); + void BufferWriter::put_sdf(bool src) { + if (src == false) put_cld(); else put_std(); } /// Store AH into flags @@ -1414,14 +1418,8 @@ namespace asmio::x86 { } /// Sets flags accordingly to the value of register given, ASMIOV extension - void BufferWriter::put_test(Location src) { - - if (src.is_simple()) { - put_test(src, src); - return; - } - - throw std::runtime_error {"Invalid operand, register expected"}; + void BufferWriter::put_test(Registry src) { + put_test(src, src); } /// Return from procedure @@ -1430,26 +1428,25 @@ namespace asmio::x86 { } /// Return from procedure and pop X bytes - void BufferWriter::put_ret(Location loc) { - if (loc.is_immediate()) { - uint32_t loc_val = loc.offset; - - if (loc_val != 0) { - put_byte(0b11000010); - put_word(loc_val); - return; - } - - return put_ret(); + void BufferWriter::put_ret(uint16_t offset) { + if (offset != 0) { + put_byte(0b11000010); + put_word(offset); + return; } - throw std::runtime_error {"Invalid operand"}; + return put_ret(); + } + + void BufferWriter::put_cpuid() { + put_byte(LONG_OPCODE); + put_byte(0xA2); } - void BufferWriter::put_xadd(Location dst, Location src) { + void BufferWriter::put_xadd(Location dst, Registry src) { - if (dst.is_memreg() && src.is_simple()) { - put_inst_std_ds(0xC0 >> 2, dst, src.base.pack(), pair_size(dst, src), false, true); + if (dst.is_memreg()) { + put_inst_std_ds(0xC0 >> 2, dst, src.pack(), pair_size(dst, src), false, true); return; } @@ -1488,10 +1485,10 @@ namespace asmio::x86 { put_byte(0x09); } - void BufferWriter::put_cmpxchg(Location dst, Location src) { + void BufferWriter::put_cmpxchg(Location dst, Registry src) { - if (dst.is_memreg() && src.is_simple()) { - put_inst_std_ds(0xB0 >> 2, dst, src.base.pack(), pair_size(dst, src), false, true); + if (dst.is_memreg()) { + put_inst_std_ds(0xB0 >> 2, dst, src.pack(), pair_size(dst, src), false, true); return; } diff --git a/src/asm/x86/instructions/fpu.cpp b/src/asmio/x86/writer_fpu.cpp similarity index 99% rename from src/asm/x86/instructions/fpu.cpp rename to src/asmio/x86/writer_fpu.cpp index 33ce12a..ce48523 100644 --- a/src/asm/x86/instructions/fpu.cpp +++ b/src/asmio/x86/writer_fpu.cpp @@ -1,4 +1,4 @@ -#include "asm/x86/writer.hpp" +#include "writer.hpp" namespace asmio::x86 { diff --git a/src/asmio/x86/writer_sse.cpp b/src/asmio/x86/writer_sse.cpp new file mode 100644 index 0000000..b19adfd --- /dev/null +++ b/src/asmio/x86/writer_sse.cpp @@ -0,0 +1,390 @@ +#include "writer.hpp" + +namespace asmio::x86 { + + void BufferWriter::put_inst_sse_2xmm(uint8_t opcode, Registry dst, Registry src) { + if (dst.is(Registry::XMM) && src.is(Registry::XMM)) { + put_inst_std(opcode, src, dst.pack(), XMMWORD, true); + return; + } + + throw std::runtime_error {"Invalid operands, expected two XMM registers"}; + } + + void BufferWriter::put_inst_sse(uint8_t opcode, Registry reg, const Location& loc) { + + if (!reg.is(Registry::XMM)) { + throw std::runtime_error {"Invalid operand, expected XMM register"}; + } + + if (loc.size != XMMWORD && loc.size != VOID) { + throw std::runtime_error {"Invalid operand, expected xmmword register or memory"}; + } + + put_inst_std(opcode, loc, reg.pack(), XMMWORD, true); + } + + void BufferWriter::put_inst_sse_sized(uint8_t opcode, Registry reg, const Location& loc, uint8_t size, bool prefix) { + + if (!reg.is(Registry::XMM)) { + throw std::runtime_error {"Invalid operand, expected XMM register"}; + } + + if ((loc.is_simple() && loc.base.is(Registry::XMM)) || (loc.is_memory() && loc.size == size)) { + if (prefix) { + put_byte(0xF3); + } + + put_inst_std(opcode, loc, reg.pack(), DWORD, true); + return; + } + + throw std::runtime_error {"Invalid operands"}; + } + + void BufferWriter::put_inst_mxcsr(const Location& loc, uint8_t opcode) { + if (loc.is_memory() && loc.size == DWORD) { + put_inst_std(0xAE, loc, RegInfo::raw(opcode), DWORD, true); + return; + } + + throw std::runtime_error {"Invalid operand, expected memory reference"}; + } + + void BufferWriter::put_inst_movxps(Location dst, Location src, uint8_t opcode) { + if (dst.is_simple() && src.is_memory()) { + if (src.size != QWORD) { + throw std::runtime_error {"Invalid operand, expected QWORD memory reference"}; + } + + put_inst_std(opcode, src, dst.base.pack(), DWORD, true); + return; + } + + if (src.is_simple() && dst.is_memory()) { + if (dst.size != QWORD) { + throw std::runtime_error {"Invalid operand, expected QWORD memory reference"}; + } + + // set the direction flag in opcode + put_inst_std(opcode | 1, dst, src.base.pack(), DWORD, true); + return; + } + + throw std::runtime_error {"Invalid operands"}; + } + + /// Move Aligned Packed f32 Values + void BufferWriter::put_movaps(Location dst, Location src) { + + if (dst.is_simple()) { + put_inst_sse(0x28, dst.base, src); + return; + } + + if (src.is_simple()) { + put_inst_sse(0x29, src.base, dst); + return; + } + + throw std::runtime_error {"Invalid operands"}; + } + + /// Move Packed f32 Values High to Low + void BufferWriter::put_movhlps(Registry dst, Registry src) { + put_inst_sse_2xmm(0x12, dst, src); + } + + /// Move Packed f32 Values Low to High + void BufferWriter::put_movlhps(Registry dst, Registry src) { + put_inst_sse_2xmm(0x16, dst, src); + } + + /// Move two packed f32 values from m64 to high quadword of dst + void BufferWriter::put_movhps(Location dst, Location src) { + put_inst_movxps(dst, src, 0x16); + } + + /// Move two packed f32 values from m64 to low quadword of dst + void BufferWriter::put_movlps(Location dst, Location src) { + put_inst_movxps(dst, src, 0x12); + } + + /// Extract Packed f32 Sign Mask + void BufferWriter::put_movmskps(Registry dst, Registry src) { + + if (!dst.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid destination operand, expected general purpose register"}; + } + + if (dst.size != QWORD && dst.size != DWORD) { + throw std::runtime_error {"Invalid destination operand, expected dword or qword register"}; + } + + if (!src.is(Registry::XMM)) { + throw std::runtime_error {"Invalid source operand, expected XMM register"}; + } + + put_inst_std(0x50, src, dst.pack(), dst.size, true); + } + + /// Move or Merge Scalar f32 Value + void BufferWriter::put_movss(Location dst, Location src) { + + if (dst.is_simple()) { + put_inst_sse_sized(0x10, dst.base, src, DWORD); + return; + } + + if (src.is_simple()) { + put_inst_sse_sized(0x11, src.base, dst, DWORD); + return; + } + + throw std::runtime_error {"Invalid operands"}; + } + + /// Move Unaligned Packed f32 Values + void BufferWriter::put_movups(Location dst, Location src) { + + if (dst.is_simple()) { + put_inst_sse(0x10, dst.base, src); + return; + } + + if (src.is_simple()) { + put_inst_sse(0x11, src.base, dst); + return; + } + + throw std::runtime_error {"Invalid operands"}; + } + + /// Add Packed f32 values + void BufferWriter::put_addps(Registry dst, Location src) { + put_inst_sse(0x58, dst, src); + } + + /// Add Scalar f32 Values + void BufferWriter::put_addss(Registry dst, Location src) { + put_inst_sse_sized(0x58, dst, src, DWORD); + } + + /// Divide Packed f32 values + void BufferWriter::put_divps(Registry dst, Location src) { + put_inst_sse(0x5E, dst, src); + } + + /// Divide Scalar f32 values + void BufferWriter::put_divss(Registry dst, Location src) { + put_inst_sse_sized(0x5E, dst, src, DWORD); + } + + /// Compute Maximum of packed f32 values + void BufferWriter::put_maxps(Registry dst, Location src) { + put_inst_sse(0x5F, dst, src); + } + + /// Compute Maximum of Scalar f32 values + void BufferWriter::put_maxss(Registry dst, Location src) { + put_inst_sse_sized(0x5F, dst, src, DWORD); + } + + /// Compute Minimum of packed f32 values + void BufferWriter::put_minps(Registry dst, Location src) { + put_inst_sse(0x5D, dst, src); + } + + /// Compute Minimum of Scalar f32 values + void BufferWriter::put_minss(Registry dst, Location src) { + put_inst_sse_sized(0x5D, dst, src, DWORD); + } + + /// Multiply Packed f32 Values + void BufferWriter::put_mulps(Registry dst, Location src) { + put_inst_sse(0x59, dst, src); + } + + /// Multiply Scalar f32 Values + void BufferWriter::put_mulss(Registry dst, Location src) { + put_inst_sse_sized(0x59, dst, src, DWORD); + } + + /// Compute Reciprocals of Packed f32 values + void BufferWriter::put_rcpps(Registry dst, Location src) { + put_inst_sse(0x53, dst, src); + } + + /// Compute Reciprocals of Scalar f32 values + void BufferWriter::put_rcpss(Registry dst, Location src) { + put_inst_sse_sized(0x53, dst, src, DWORD); + } + + /// Compute Square Root Reciprocals of Packed f32 values + void BufferWriter::put_rsqrtps(Registry dst, Location src) { + put_inst_sse(0x52, dst, src); + } + + /// Compute Square Root Reciprocals of Scalar f32 values + void BufferWriter::put_rsqrtss(Registry dst, Location src) { + put_inst_sse_sized(0x52, dst, src, DWORD); + } + + /// Compute Square Roots of Packed f32 values + void BufferWriter::put_sqrtps(Registry dst, Location src) { + put_inst_sse(0x51, dst, src); + } + + /// Compute Square Roots of Scalar f32 values + void BufferWriter::put_sqrtss(Registry dst, Location src) { + put_inst_sse_sized(0x51, dst, src, DWORD); + } + + /// Subtract Packed f32 values + void BufferWriter::put_subps(Registry dst, Location src) { + put_inst_sse(0x5C, dst, src); + } + + /// Subtract Scalar f32 values + void BufferWriter::put_subss(Registry dst, Location src) { + put_inst_sse_sized(0x5C, dst, src, DWORD); + } + + /// Compare Packed f32 values + void BufferWriter::put_cmpps(Registry dst, Location src, SimdCondition cond) { + put_inst_sse(0xC2, dst, src); + put_byte(static_cast(cond)); + } + + /// Compare Scalar f32 values + void BufferWriter::put_cmpss(Registry dst, Location src, SimdCondition cond) { + put_inst_sse_sized(0xC2, dst, src, DWORD); + put_byte(static_cast(cond)); + } + + /// Compare Scalar Ordered f32 Values and Set EFLAGS + void BufferWriter::put_comiss(Registry dst, Location src) { + put_inst_sse_sized(0x2F, dst, src, DWORD, false); + } + + /// Compare Scalar Unordered f32 Values and Set EFLAGS + void BufferWriter::put_ucomiss(Registry dst, Location src) { + put_inst_sse_sized(0x2E, dst, src, DWORD, false); + } + + /// Bitwise logical AND NOT of packed dword values + void BufferWriter::put_andnps(Registry dst, Location src) { + put_inst_sse(0x55, dst, src); + } + + /// Bitwise logical AND of packed dword values + void BufferWriter::put_andps(Registry dst, Location src) { + put_inst_sse(0x54, dst, src); + } + + /// Bitwise logical OR of packed dword values + void BufferWriter::put_orps(Registry dst, Location src) { + put_inst_sse(0x56, dst, src); + } + + /// Bitwise logical XOR of packed dword values + void BufferWriter::put_xorps(Registry dst, Location src) { + put_inst_sse(0x57, dst, src); + } + + /// Packed Interleave Shuffle of Quadruplets of f32 Values + void BufferWriter::put_shufps(Registry dst, Location src, uint8_t selector) { + put_inst_sse(0xC6, dst, src); + put_byte(selector); + } + + /// Unpack and Interleave High Packed f32 Values + void BufferWriter::put_unpckhps(Registry dst, Location src) { + put_inst_sse(0x15, dst, src); + } + + /// Unpack and Interleave Low Packed f32 Values + void BufferWriter::put_unpcklps(Registry dst, Location src) { + put_inst_sse(0x14, dst, src); + } + + /// Convert Doubleword Integer to Scalar f32 Value + void BufferWriter::put_cvtsi2ss(Registry dst, Location src) { + put_byte(0xF3); + put_inst_std(0x2A, src, dst.pack(), src.size, true); + } + + /// Convert Scalar f32 Value to Doubleword Integer + void BufferWriter::put_cvtss2si(Registry dst, Location src) { + + if (!dst.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid destination operand, expected general purpose register"}; + } + + if (dst.size != DWORD && dst.size != QWORD) { + throw std::runtime_error {"Invalid destination operand, expected dword or qword register"}; + } + + if (src.is_simple() && !src.base.is(Registry::XMM)) { + throw std::runtime_error {"Invalid source operand, expected XMM register"}; + } + + if (src.is_memory() && (src.size != VOID && src.size != DWORD)) { + throw std::runtime_error {"Invalid source operand, dword memory reference"}; + } + + put_byte(0xF3); + put_inst_std(0x2D, src, dst.pack(), dst.size, true); + } + + /// Convert With Truncation Scalar f32 Value to Integer + void BufferWriter::put_cvttss2si(Registry dst, Location src) { + + if (!dst.is(Registry::GENERAL)) { + throw std::runtime_error {"Invalid destination operand, expected general purpose register"}; + } + + if (dst.size != DWORD && dst.size != QWORD) { + throw std::runtime_error {"Invalid destination operand, expected dword or qword register"}; + } + + if (src.is_simple() && !src.base.is(Registry::XMM)) { + throw std::runtime_error {"Invalid source operand, expected XMM register"}; + } + + if (src.is_memory() && (src.size != VOID && src.size != DWORD)) { + throw std::runtime_error {"Invalid source operand, dword memory reference"}; + } + + put_byte(0xF3); + put_inst_std(0x2C, src, dst.pack(), dst.size, true); + } + + /// Load MXCSR Register from src + void BufferWriter::put_ldmxcsr(Location src) { + put_inst_mxcsr(src, 2); + } + + /// Store MXCSR Register into dst + void BufferWriter::put_stmxcsr(Location dst) { + put_inst_mxcsr(dst, 3); + } + + /// Store Packed f32 Values Using Non-Temporal Hint + void BufferWriter::put_movntps(Location dst, Registry src) { + if (dst.is_memory()) { + put_inst_sse(0x2B, src, dst); + return; + } + + throw std::runtime_error {"Invalid destination operand, expected memory reference"}; + } + + /// Store Fence + void BufferWriter::put_sfence() { + put_byte(0x0F); + put_byte(0xAE); + put_byte(0xF8); + } + +} \ No newline at end of file diff --git a/src/macro.hpp b/src/macro.hpp deleted file mode 100644 index 3a4bb74..0000000 --- a/src/macro.hpp +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#define JOIN_PARTS_RESOLVED(first, second) first##second - -/// Allows one to merge two tokens, even macro values -#define JOIN_PARTS(first, second) JOIN_PARTS_RESOLVED(first, second) - -/// Mark that no padding should be used in marked struct, ever -#define PACKED __attribute__((__packed__)) \ No newline at end of file diff --git a/src/out/buffer/sizes.hpp b/src/out/buffer/sizes.hpp deleted file mode 100644 index a13358e..0000000 --- a/src/out/buffer/sizes.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include "external.hpp" - -namespace asmio { - - enum Size : uint8_t { - VOID = 0, - BYTE = 1, - WORD = 2, - DWORD = 4, - QWORD = 8, - TWORD = 10, - }; - -} \ No newline at end of file diff --git a/src/out/elf/object.cpp b/src/out/elf/object.cpp deleted file mode 100644 index 354da88..0000000 --- a/src/out/elf/object.cpp +++ /dev/null @@ -1,132 +0,0 @@ - -#include "object.hpp" - -#include - -namespace asmio { - - std::ostream& operator<<(std::ostream& os, RunStatus c) { - switch (c) { - case RunStatus::SUCCESS: return os << "SUCCESS"; - case RunStatus::ARGS_ERROR: return os << "ARGS_ERROR"; - case RunStatus::MEMFD_ERROR: return os << "MEMFD_ERROR"; - case RunStatus::SEAL_ERROR: return os << "SEAL_ERROR"; - case RunStatus::FORK_ERROR: return os << "FORK_ERROR"; - case RunStatus::EXEC_ERROR: return os << "EXEC_ERROR"; - case RunStatus::WAIT_ERROR: return os << "WAIT_ERROR"; - default: return os << "UNKNOWN"; - } - } - - std::ostream& operator<<(std::ostream& os, const RunResult& result) { - return os << "RunResult{status=" << result.type << ", return=" << result.status << "}"; - } - - /* - * class ObjectFile - */ - - ObjectFile::ObjectFile(std::vector&& image) - : image(std::move(image)) { - } - - ObjectFile::ObjectFile(const std::vector& image) - : image(image) { - } - - bool ObjectFile::save(const std::string& path) const { - using std::filesystem::perms; - - // if file creation fails return false - try { - std::ofstream output {path}; - - if (output.bad()) { - return false; - } - - output.write(reinterpret_cast(image.data()), image.size()); - output.close(); - } catch (const std::exception&) { - return false; - } - - // this part is best-effort only - try { - const perms flags = perms::owner_exec | perms::group_exec | perms::others_exec; - std::filesystem::permissions(path, flags, std::filesystem::perm_options::add); - } catch (const std::exception&) {} - - // file was created - return true; - } - - const std::vector& ObjectFile::bytes() const { - return image; - } - - RunResult ObjectFile::execute(const char* name) const { - const char* argv[] = {name, nullptr}; - return execute(argv, const_cast(environ)); - } - - RunResult ObjectFile::execute(const char** argv, const char** envp) const { - // verify arguments, status can be a nullptr - if (argv == nullptr || envp == nullptr) { - return RunStatus::ARGS_ERROR; - } - - // create in-memory file descriptor - const int memfd = memfd_create("buffer", MFD_ALLOW_SEALING | MFD_CLOEXEC); - if (memfd == -1) { - return RunStatus::MEMFD_ERROR; - } - - int* flag = (int*) mmap(nullptr, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); - if (flag == nullptr) { - return RunStatus::MMAP_ERROR; - } - - // copy buffer into memfd - write(memfd, image.data(), image.size()); - - // we use this to check if the child really run or did execve just fail - *flag = 0; - - // add seals to memfd - if (fcntl(memfd, F_ADD_SEALS, F_SEAL_WRITE | F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL) != 0) { - return RunStatus::SEAL_ERROR; - } - - const pid_t pid = fork(); - if (pid == -1) { - return RunStatus::FORK_ERROR; - } - - // replace child with memfd elf file - if (pid == 0) { - fexecve(memfd, const_cast(argv), const_cast(envp)); - - // if fexecve fails we need to kill ourselves - *flag = 1; - exit(1); - } - - int status = 0; - - // wait for child and get status code - if (waitpid(pid, &status, 0) == -1) { - return RunStatus::WAIT_ERROR; - } - - if (*flag) { - return RunStatus::EXEC_ERROR; - } - - munmap(flag, sizeof(int)); - - // obtain return code from child status - return WEXITSTATUS(status); - } - -} diff --git a/src/tasml/args.hpp b/src/tasml/args.hpp index 1a2d31f..729448b 100644 --- a/src/tasml/args.hpp +++ b/src/tasml/args.hpp @@ -1,7 +1,7 @@ #pragma once -#include "external.hpp" -#include "util.hpp" +#include "../asmio/external.hpp" +#include "../asmio/util.hpp" namespace tasml { diff --git a/src/tasml/error.hpp b/src/tasml/error.hpp index 7147d97..39c1227 100644 --- a/src/tasml/error.hpp +++ b/src/tasml/error.hpp @@ -1,7 +1,7 @@ #pragma once -#include "external.hpp" -#include "out/buffer/segmented.hpp" +#include +#include namespace tasml { diff --git a/src/tasml/main.cpp b/src/tasml/main.cpp index ba18a2d..a729196 100644 --- a/src/tasml/main.cpp +++ b/src/tasml/main.cpp @@ -1,13 +1,13 @@ -#include "external.hpp" -#include "util.hpp" +#include +#include #include "args.hpp" #include "error.hpp" -#include +#include // private libs #include #include -#include +#include #include "top.hpp" diff --git a/src/tasml/stream.hpp b/src/tasml/stream.hpp index 000be6c..88bbb76 100644 --- a/src/tasml/stream.hpp +++ b/src/tasml/stream.hpp @@ -1,6 +1,6 @@ #pragma once -#include "external.hpp" +#include "../asmio/external.hpp" #include "predicate.hpp" #include "token.hpp" diff --git a/src/tasml/token.cpp b/src/tasml/token.cpp index 22ca45d..91fd918 100644 --- a/src/tasml/token.cpp +++ b/src/tasml/token.cpp @@ -1,7 +1,7 @@ #include "token.hpp" -#include +#include namespace tasml { diff --git a/src/tasml/token.hpp b/src/tasml/token.hpp index b25811f..4383f55 100644 --- a/src/tasml/token.hpp +++ b/src/tasml/token.hpp @@ -1,6 +1,6 @@ #pragma once -#include "external.hpp" +#include "../asmio/external.hpp" namespace tasml { diff --git a/src/tasml/tokenizer.cpp b/src/tasml/tokenizer.cpp index 415147c..0bc4ad2 100644 --- a/src/tasml/tokenizer.cpp +++ b/src/tasml/tokenizer.cpp @@ -1,5 +1,5 @@ #include "tokenizer.hpp" -#include "util.hpp" +#include "../asmio/util.hpp" // private library #include diff --git a/src/tasml/tokenizer.hpp b/src/tasml/tokenizer.hpp index 2699539..0d56175 100644 --- a/src/tasml/tokenizer.hpp +++ b/src/tasml/tokenizer.hpp @@ -1,6 +1,6 @@ #pragma once -#include "external.hpp" +#include "../asmio/external.hpp" #include "token.hpp" #include "error.hpp" diff --git a/src/tasml/top.cpp b/src/tasml/top.cpp index fa195ca..12f79c6 100644 --- a/src/tasml/top.cpp +++ b/src/tasml/top.cpp @@ -1,7 +1,7 @@ #include "top.hpp" -#include -#include +#include +#include #include "tokenizer.hpp" diff --git a/src/tasml/top.hpp b/src/tasml/top.hpp index b5b66a7..bdd0ac9 100644 --- a/src/tasml/top.hpp +++ b/src/tasml/top.hpp @@ -1,6 +1,6 @@ #pragma once -#include -#include +#include +#include #include "error.hpp" #include "stream.hpp" diff --git a/test/aarch64.cpp b/test/aarch64.cpp index 5dda06a..24f17f7 100644 --- a/test/aarch64.cpp +++ b/test/aarch64.cpp @@ -1,19 +1,14 @@ -#define DEBUG_MODE false -#define VSTL_TEST_COUNT 3 -#define VSTL_PRINT_SKIP_REASON true -#define VSTL_SUBMODULE true -#include "vstl.hpp" -#include "asm/aarch64/writer.hpp" -#include "out/buffer/executable.hpp" -#include +#include +#include +#include +#include #include -#include #include "test.hpp" +#include "vstl.hpp" namespace test { - using namespace asmio; using namespace asmio::arm; @@ -49,11 +44,20 @@ namespace test { SegmentedBuffer segmented; BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::AARCH64; + + writer.put_orr(X1, X2, X3); + writer.put_orn(X1, X2, X3); + writer.put_movn(X12, X11); EXPECT_THROW(std::runtime_error) { writer.put_orr(W(0), W(0), X(0)); }; + segmented.link(0); + std::vector s0 = {0x41, 0x00, 0x03, 0xaa, 0x41, 0x00, 0x23, 0xaa, 0xec, 0x03, 0x2b, 0xaa}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + }; TEST (writer_fail_orr_imm_invalid) { @@ -148,6 +152,473 @@ namespace test { }; + TEST (writer_check_cas) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + + // considered valid + writer.put_cas(X0, XZR, XZR); + writer.put_casb(X0, X1, W2); + writer.put_cash(X0, X1, X2, Order::RELEASE); + writer.put_cash(X0, X1, X2, Order::ACQUIRE_RELEASE); + + EXPECT_THROW(std::runtime_error) { + writer.put_cas(X0, X1, W2); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_casb(XZR, X1, W1); + }; + + segmented.link(0); + std::vector s0 = {0x1f, 0x7c, 0xbf, 0xc8, 0x01, 0x7c, 0xa2, 0x08, 0x01, 0xfc, 0xa2, 0x48, 0x01, 0xfc, 0xe2, 0x48}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_ldar) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + + // considered valid + writer.put_ldarb(X11, SP); + writer.put_ldarh(WZR, X10); + writer.put_ldar(W10, SP); + writer.put_ldar(X10, SP); + + EXPECT_THROW(std::runtime_error) { + writer.put_ldarb(X0, XZR); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_ldar(X1, W1); // source ptr must be qword + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_ldar(SP, X10); + }; + + segmented.link(0); + std::vector s0 = {0xeb, 0xff, 0xdf, 0x08, 0x5f, 0xfd, 0xdf, 0x48, 0xea, 0xff, 0xdf, 0x88, 0xea, 0xff, 0xdf, 0xc8}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_ldadd) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::AARCH64; + + writer.put_ldadd(X0, X1, X2); + writer.put_ldadd(W11, W1, SP); + writer.put_ldadd(WZR, WZR, X2); + writer.put_ldaddb(X11, X1, X2); + writer.put_ldaddb(W0, X1, X2); + writer.put_ldaddb(X11, W0, X2); + writer.put_ldaddb(W2, W0, X2); + + EXPECT_THROW(std::runtime_error) { + writer.put_ldadd(X11, W1, X2); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_ldaddb(X11, X1, W2); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_ldaddb(SP, X1, X1); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_ldaddb(X1, SP, X1); + }; + + segmented.link(0); + std::vector s0 = {0x41, 0x00, 0x20, 0xf8, 0xe1, 0x03, 0x2b, 0xb8, 0x5f, 0x00, 0x3f, 0xb8, 0x41, 0x00, 0x2b, 0x38, 0x41, 0x00, 0x20, 0x38, 0x40, 0x00, 0x2b, 0x38, 0x40, 0x00, 0x22, 0x38}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_ldp_ldpsw) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::AARCH64; + + // ldp + writer.put_ldp(X2, X3, X0); + writer.put_ldp(W2, W3, X0); + writer.put_ldp(W2, W3, SP); + writer.put_ldpi(W2, W3, X0, 12); + writer.put_ildp(X2, X3, X0, -8); + + // ldpsw + writer.put_ldpsw(X2, X3, X0); + writer.put_ldpswi(X2, X3, X0, 8); + writer.put_ildpsw(X2, X3, X0, -8); + + EXPECT_THROW(std::runtime_error) { + writer.put_ldp(W2, X3, X0); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_ildp(X3, X3, X0, 12); // 12 is not div by 8 + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_ldp(X2, W3, X0); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_ldp(X1, X2, W4); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_ldpi(X1, X2, X4, 512); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_ldpsw(W2, W3, X0); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_ildpsw(W2, W3, X0, 260); + }; + + segmented.link(0); + std::vector s0 = {0x02, 0x0c, 0x40, 0xa9, 0x02, 0x0c, 0x40, 0x29, 0xe2, 0x0f, 0x40, 0x29, 0x02, 0x8c, 0xc1, 0x28, 0x02, 0x8c, 0xff, 0xa9, 0x02, 0x0c, 0x40, 0x69, 0x02, 0x0c, 0xc1, 0x68, 0x02, 0x0c, 0xff, 0x69}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_ldnp) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::AARCH64; + + writer.put_ldnp(X1, X2, X3, 16); + writer.put_ldnp(W1, W2, X3, 12); + + EXPECT_THROW(std::runtime_error) { + writer.put_ldnp(X1, X2, X3, 12); + }; + + segmented.link(0); + std::vector s0 = {0x61, 0x08, 0x41, 0xa8, 0x61, 0x88, 0x41, 0x28}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_ldxp) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::AARCH64; + + writer.put_ldxp(X1, X2, X3); + writer.put_ldxp(W1, W2, X3); + + EXPECT_THROW(std::runtime_error) { + writer.put_ldxp(W1, X2, X3); + }; + + segmented.link(0); + std::vector s0 = {0x61, 0x08, 0x7f, 0xc8, 0x61, 0x08, 0x7f, 0x88}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_stp_stnp) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::AARCH64; + + writer.put_stp(X1, X2, X3); + writer.put_stp(W1, W2, X3, 128); + writer.put_stpi(X3, X4, X3, 32); + writer.put_istp(W1, W2, X3, 64); + writer.put_stnp(X1, X2, X3, 48); + + segmented.link(0); + std::vector s0 = {0x61, 0x08, 0x00, 0xa9, 0x61, 0x08, 0x10, 0x29, 0x63, 0x10, 0x82, 0xa8, 0x61, 0x08, 0x88, 0x29, 0x61, 0x08, 0x03, 0xa8}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_eret_hlt_hvc) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::AARCH64; + + writer.put_eret(); + writer.put_clrex(); + writer.put_hlt(0x42); + writer.put_hvc(0x69); + + segmented.link(0); + std::vector s0 = {0xe0, 0x03, 0x9f, 0xd6, 0x5f, 0x3f, 0x03, 0xd5, 0x40, 0x08, 0x40, 0xd4, 0x22, 0x0d, 0x00, 0xd4}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_ldops) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::AARCH64; + + writer.put_ldaddb(W1, W2, X8, Order::ACQUIRE); + writer.put_ldclr(X5, X6, X11, Order::RELEASE); + writer.put_ldeor(X3, X24, X4, Order::ACQUIRE_RELEASE); + writer.put_ldseth(X6, X6, X6, Order::NONE); + writer.put_ldsmax(X1, X5, X9, Order::RELEASE); + writer.put_ldumax(X2, X6, X10, Order::ACQUIRE); + writer.put_ldsmin(X3, X7, X11, Order::NONE); + writer.put_ldumin(X4, X8, X12, Order::ACQUIRE_RELEASE); + + segmented.link(0); + std::vector s0 = {0x02, 0x01, 0xa1, 0x38, 0x66, 0x11, 0x65, 0xf8, 0x98, 0x20, 0xe3, 0xf8, 0xc6, 0x30, 0x26, 0x78, 0x25, 0x41, 0x61, 0xf8, 0x46, 0x61, 0xa2, 0xf8, 0x67, 0x51, 0x23, 0xf8, 0x88, 0x71, 0xe4, 0xf8}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_swp) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::AARCH64; + + writer.put_swpb(W1, W2, X8, Order::ACQUIRE); + writer.put_swph(X5, X6, X11, Order::RELEASE); + writer.put_swp(X3, X24, X4, Order::ACQUIRE_RELEASE); + writer.put_swp(W3, W24, X4, Order::NONE); + + segmented.link(0); + std::vector s0 = {0x02, 0x81, 0xa1, 0x38, 0x66, 0x81, 0x65, 0x78, 0x98, 0x80, 0xe3, 0xf8, 0x98, 0x80, 0x23, 0xb8}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_ccmp_ccmn) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::AARCH64; + + writer.put_ccmp(Condition::LE, Condition::NE, X2, X7); + writer.put_ccmn(Condition::GE, Condition::CC, X5, X3); + writer.put_ccmp(Condition::LE, Condition::NE, X2, 15); + writer.put_ccmn(Condition::CC, Condition::CC, X5, 3); + + // check overflow + writer.put_ccmn(Condition::CC, Condition::CC, X5, 0xff); + writer.put_ccmp(Condition::CC, Condition::CC, X5, 0xff); + + EXPECT_THROW(std::runtime_error) { + writer.put_ccmp(Condition::LE, Condition::NE, X2, W7); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_ccmn(Condition::GE, Condition::CC, W5, X3); + }; + + segmented.link(0); + std::vector s0 = {0x41, 0xd0, 0x47, 0xfa, 0xa3, 0xa0, 0x43, 0xba, 0x41, 0xd8, 0x4f, 0xfa, 0xa3, 0x38, 0x43, 0xba, 0xa3, 0x38, 0x5f, 0xba, 0xa3, 0x38, 0x5f, 0xfa}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_add_sub_imm) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + + writer.put_add(X1, X8, 7, true); + writer.put_adds(X2, X7, 0xfff); + writer.put_sub(X3, X6, 700); + writer.put_subs(X4, X5, 0xfff, true); + + segmented.link(0); + std::vector s0 = {0x01, 0x1d, 0x40, 0x91, 0xe2, 0xfc, 0x3f, 0xb1, 0xc3, 0xf0, 0x0a, 0xd1, 0xa4, 0xfc, 0x7f, 0xf1}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_add_sub_shifted) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + + writer.put_add(X1, X8, X9, ShiftType::LSL, 3); + writer.put_adds(X2, X7, X10, ShiftType::ASR, 5); + writer.put_sub(X3, X6, X11, ShiftType::LSR, 7); + writer.put_subs(X4, X5, X12, ShiftType::LSL, 0); + + segmented.link(0); + std::vector s0 = {0x01, 0x0d, 0x09, 0x8b, 0xe2, 0x14, 0x8a, 0xab, 0xc3, 0x1c, 0x4b, 0xcb, 0xa4, 0x00, 0x0c, 0xeb}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_csinv_cinv_csetm) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + + writer.put_csinv(Condition::PL, X0, X2, X3); + writer.put_csinc(Condition::GE, X6, X7, X8); + writer.put_cinv(Condition::HI, X0, X6); + writer.put_csetm(Condition::LE, X3); + writer.put_csneg(Condition::LE, X0, X2, X3); + writer.put_cneg(Condition::LE, X0, X11); + + segmented.link(0); + std::vector s0 = {0x40, 0x50, 0x83, 0xda, 0xe6, 0xa4, 0x88, 0x9a, 0xc0, 0x90, 0x86, 0xda, 0xe3, 0xc3, 0x9f, 0xda, 0x40, 0xd4, 0x83, 0xda, 0x60, 0xd5, 0x8b, 0xda}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_cmn_cmp) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + + writer.put_cmn(X2, 0x7b); + writer.put_cmn(X6, X7, ShiftType::LSL, 8); + writer.put_cmp(X2, 0x1a4); + writer.put_cmp(X6, X7, ShiftType::LSL, 8); + + segmented.link(0); + std::vector s0 = {0x5f, 0xec, 0x01, 0xb1, 0xdf, 0x20, 0x07, 0xab, 0x5f, 0x90, 0x06, 0xf1, 0xdf, 0x20, 0x07, 0xeb}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_eon) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + + writer.put_eon(X2, X3, X4, ShiftType::LSL, 3); + + segmented.link(0); + std::vector s0 = {0x62, 0x0c, 0x24, 0xca}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_exclusive_ops) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::AARCH64; + + writer.put_ldxr(X3, X7); + writer.put_ldxrb(X3, X7); + writer.put_ldaxrh(X3, X7); + writer.put_stlxr(W1, X2, X5); + writer.put_stlxp(W1, X2, X3, X5); + writer.put_stxp(W1, X2, X3, X5); + + segmented.link(0); + std::vector s0 = {0xe3, 0x7c, 0x5f, 0xc8, 0xe3, 0x7c, 0x5f, 0x08, 0xe3, 0xfc, 0x5f, 0x48, 0xa2, 0xfc, 0x01, 0xc8, 0xa2, 0x8c, 0x21, 0xc8, 0xa3, 0x0c, 0x21, 0xc8}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_ldtr_sttr_ldur_stur) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::AARCH64; + + writer.put_ldtr(X0, X11, 123); + writer.put_ldtrh(X1, X10, -123); + writer.put_ldtrb(X2, X9, -123); + writer.put_ldur(W3, X8, 123); + writer.put_ldurh(X4, X7, 123); + writer.put_ldurb(X5, X6, 123); + writer.put_sttr(X0, X11, 123); + writer.put_sttrh(X1, X10, 123); + writer.put_sttrb(X2, X9, 123); + writer.put_stur(X3, X8, 123); + writer.put_sturh(X4, X7, 123); + writer.put_sturb(X5, X6, 123); + + EXPECT_THROW(std::runtime_error) { + writer.put_ldtr(X0, X1, 256); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_ldtr(X0, XZR, 6); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_ldtr(X0, W2, 6); + }; + + segmented.link(0); + std::vector s0 = {0x60, 0xb9, 0x47, 0xf8, 0x41, 0x59, 0x58, 0x78, 0x22, 0x59, 0x58, 0x38, 0x03, 0xb1, 0x47, 0xb8, 0xe4, 0xb0, 0x47, 0x78, 0xc5, 0xb0, 0x47, 0x38, 0x60, 0xb9, 0x07, 0xf8, 0x41, 0xb9, 0x07, 0x78, 0x22, 0xb9, 0x07, 0x38, 0x03, 0xb1, 0x07, 0xf8, 0xe4, 0xb0, 0x07, 0x78, 0xc5, 0xb0, 0x07, 0x38}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_neg_ngc) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::AARCH64; + + writer.put_neg(X1, X7); + writer.put_negs(X2, X8); + writer.put_ngc(X3, X9); + writer.put_ngcs(X4, X10); + + writer.put_neg(X5, X11, ShiftType::LSL, 4); + writer.put_negs(X6, X12, ShiftType::LSL, 4); + + segmented.link(0); + std::vector s0 = {0xe1, 0x03, 0x07, 0xcb, 0xe2, 0x03, 0x08, 0xeb, 0xe3, 0x03, 0x09, 0xda, 0xe4, 0x03, 0x0a, 0xfa, 0xe5, 0x13, 0x0b, 0xcb, 0xe6, 0x13, 0x0c, 0xeb}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_sxt) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::AARCH64; + + writer.put_sxtb(X1, X7); + writer.put_sxth(X2, X8); + writer.put_sxtw(X2, X9); + + segmented.link(0); + std::vector s0 = {0xe1, 0x1c, 0x40, 0x93, 0x02, 0x3d, 0x40, 0x93, 0x22, 0x7d, 0x40, 0x93}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + + TEST (writer_check_prfm_basic) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + segmented.elf_machine = ElfMachine::AARCH64; + + writer.put_prfm(Prefetch::PLD_L1_STRM, X1, X7); + + segmented.link(0); + std::vector s0 = {0x21, 0x68, 0xa7, 0xf8}; + CHECK(segmented.segments()[0].buffer, s0); // .rwx + + }; + /* * region Executable * Begin architecture depended tests for ARM @@ -155,6 +626,20 @@ namespace test { #if ARCH_AARCH64 + TEST (check_no_f80) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + writer.put_ret(); + + auto exe = to_executable(segmented); + + EXPECT_THROW(std::runtime_error) { + exe.call_f80(); + }; + + } + TEST (writer_exec_nop_ret) { SegmentedBuffer segmented; @@ -1708,6 +2193,166 @@ namespace test { }; + TEST (writer_exec_cas) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + + writer.section(MemoryFlag::R | MemoryFlag::W); + writer.label("cell"); + writer.put_qword(11); + + writer.section(MemoryFlag::R | MemoryFlag::X); + writer.label("a"); + writer.put_adr(X0, "cell"); + writer.put_mov(X1, 13); + writer.put_mov(X2, 12); + writer.put_cas(X0, X1, X2, Order::ACQUIRE); // ordering not actually required + writer.put_ldr(X0, "cell"); + writer.put_ret(); + + writer.section(MemoryFlag::R | MemoryFlag::X); + writer.label("b"); + writer.put_adr(X0, "cell"); + writer.put_mov(X1, 12); + writer.put_mov(X2, 11); + writer.put_cas(X0, X1, X2, Order::ACQUIRE_RELEASE); // ordering not actually required + writer.put_ldr(X0, "cell"); + writer.put_ret(); + +#ifdef __ARM_FEATURE_ATOMICS + auto exec = to_executable(segmented); + CHECK(exec.call_u64("a"), 11); + CHECK(exec.call_u64("b"), 12); + CHECK(exec.call_u64("a"), 13); + CHECK(exec.call_u64("b"), 13); + CHECK(exec.call_u64("a"), 13); +#else + SKIP("AArch64 LSE not supported") +#endif + + }; + + TEST (writer_exec_ldar) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + + writer.section(MemoryFlag::R | MemoryFlag::W); + writer.label("cell"); + writer.put_byte(42); + + writer.section(MemoryFlag::R | MemoryFlag::X); + writer.label("a"); + writer.put_adr(X1, "cell"); + writer.put_ldarb(X0, X1); + writer.put_ret(); + + auto exec = to_executable(segmented); + CHECK(exec.call_u64("a"), 42); + + }; + + TEST (writer_exec_ldadd) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + + writer.section(MemoryFlag::R | MemoryFlag::W); + writer.label("cell"); + writer.put_dword(0x1234); + + writer.section(MemoryFlag::R | MemoryFlag::X); + writer.label("add"); + writer.put_adr(X1, "cell"); + writer.put_mov(X2, 0x1111); + writer.put_ldadd(X2, X0, X1); + writer.put_ret(); + +#ifdef __ARM_FEATURE_ATOMICS + auto exec = to_executable(segmented); + CHECK(exec.call_u64("add"), 0x2345); + CHECK(exec.call_u64("add"), 0x3456); + CHECK(exec.call_u64("add"), 0x4567); +#else + SKIP("AArch64 LSE not supported") +#endif + + }; + + TEST (writer_exec_ldpsw_ldp) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + + writer.section(MemoryFlag::R | MemoryFlag::W); + writer.label("cell"); + writer.put_dword(234); + writer.put_dword(-312); + + writer.section(MemoryFlag::R | MemoryFlag::X); + writer.label("a"); + writer.put_adr(X3, "cell"); + writer.put_ldpsw(X1, X2, X3); + writer.put_add(X0, X1, X2); + writer.put_ret(); + + writer.section(MemoryFlag::R | MemoryFlag::X); + writer.label("b"); + writer.put_adr(X3, "cell"); + writer.put_ldp(W1, W0, X3); + writer.put_ret(); + + auto exec = to_executable(segmented); + CHECK(exec.call_u64("b"), uint32_t(-312)); + + }; + + TEST (writer_exec_add_sub_imm) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + + writer.put_mov(X1, 13); + writer.put_add(X2, X1, 7); + writer.put_sub(X3, X1, 3); + writer.put_add(X0, X2, X3); + writer.put_ret(); + + auto exec = to_executable(segmented); + CHECK(exec.call_i64(), 30); + + }; + + TEST (writer_exec_add_shifted) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + + writer.put_mov(X1, 0x17); + writer.put_mov(X2, 0x03); + writer.put_add(X0, X2, X1, ShiftType::LSL, 4); + writer.put_ret(); + + auto exec = to_executable(segmented); + CHECK(exec.call_i64(), 0x173); + + }; + + TEST (writer_exec_sxtb) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + + writer.put_mov(X1, 0xff); + writer.put_sxtb(X0, X1); + writer.put_ret(); + + auto exec = to_executable(segmented); + CHECK(exec.call_i64(), -1); + + }; + #endif } \ No newline at end of file diff --git a/test/elf.cpp b/test/elf.cpp index 48c2850..3b7899e 100644 --- a/test/elf.cpp +++ b/test/elf.cpp @@ -1,17 +1,13 @@ -#define VSTL_TEST_COUNT 3 -#define VSTL_PRINT_SKIP_REASON true -#define VSTL_SUBMODULE true - #include -#include -#include -#include -#include +#include +#include +#include +#include #include -#include "util/tmp.hpp" -#include "out/elf/export.hpp" +#include +#include #include "test.hpp" namespace test { @@ -32,6 +28,7 @@ namespace test { ASSERT(!result.contains("Warning")); ASSERT(!result.contains("Error")); + ASSERT(!result.empty()); ASSERT(result.contains("ELF64")); ASSERT(result.contains("Advanced Micro Devices X86-64")); @@ -87,6 +84,7 @@ namespace test { ASSERT(!result.contains("Warning")); ASSERT(!result.contains("Error")); + ASSERT(!result.empty()); ASSERT(result.contains("0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND")); ASSERT(result.contains("1: 00000000000000db 1 OBJECT LOCAL DEFAULT 2 arda")); @@ -191,6 +189,7 @@ namespace test { ASSERT(!result.contains("Warning")); ASSERT(!result.contains("Error")); + ASSERT(!result.empty()); ASSERT(result.contains("[ 2] .rwx PROGBITS")); ASSERT(result.contains("[ 3] .rodata PROGBITS")); @@ -243,8 +242,8 @@ namespace test { // link with our object util::TempFile exec {".out"}; - std::string gcc_output = call_shell("gcc -z noexecstack -o " + exec.path() + " " + object.path() + " " + main_src.path() ); - CHECK(gcc_output, ""); + std::string gcc_output = call_shell("gcc -o " + exec.path() + " " + object.path() + " " + main_src.path()); + ASSERT(!gcc_output.contains("error")); std::string exe_output = call_shell(exec.path()); CHECK(exe_output, "hello!"); @@ -314,6 +313,7 @@ namespace test { ASSERT(!result.contains("Warning")); ASSERT(!result.contains("Error")); + ASSERT(!result.empty()); auto line_block = util::split_string(result, "Line Number Statements:").at(1); auto lines = util::normalize_strings(util::split_string(line_block)); @@ -339,7 +339,11 @@ namespace test { "[0x00000069] Extended opcode 1: End of Sequence", }; - CHECK(lines, expected); + ASSERT(lines.size() >= expected.size()); + + for (size_t i = 0; i < expected.size(); i ++) { + CHECK(lines[i], expected[i]); + } }; @@ -363,6 +367,7 @@ namespace test { ASSERT(!result.contains("Warning")); ASSERT(!result.contains("Error")); + ASSERT(!result.empty()); auto line_block = util::split_string(result, "Line Number Statements:").at(1); auto lines = util::normalize_strings(util::split_string(line_block)); @@ -376,7 +381,11 @@ namespace test { "[0x00000042] Extended opcode 1: End of Sequence" }; - CHECK(lines, expected); + ASSERT(lines.size() >= expected.size()); + + for (size_t i = 0; i < expected.size(); i ++) { + CHECK(lines[i], expected[i]); + } }; @@ -418,6 +427,7 @@ namespace test { ASSERT(!result.contains("Warning")); ASSERT(!result.contains("Error")); + ASSERT(!result.empty()); ASSERT(result.contains("1 DW_TAG_base_type [no children]")); ASSERT(result.contains("2 DW_TAG_pointer_type [no children]")); @@ -464,6 +474,7 @@ namespace test { ASSERT(!result.contains("Warning")); ASSERT(!result.contains("Error")); + ASSERT(!result.empty()); ASSERT(result.contains("Abbrev Number: 1 (DW_TAG_compile_unit)")); ASSERT(result.contains("Abbrev Number: 2 (DW_TAG_base_type)")); diff --git a/test/tasml.cpp b/test/tasml.cpp index bf9d7ca..e402722 100644 --- a/test/tasml.cpp +++ b/test/tasml.cpp @@ -1,19 +1,14 @@ -#define DEBUG_MODE false -#define VSTL_TEST_COUNT 3 -#define VSTL_PRINT_SKIP_REASON true -#define VSTL_SUBMODULE true - #include #include #include #include -#include -#include +#include +#include #include "test.hpp" #include "vstl.hpp" -#include "out/buffer/executable.hpp" +#include namespace test { @@ -51,6 +46,21 @@ namespace test { }; + TEST (tasml_check_order_semantics) { + + std::string code = R"( + lang aarch64 + cas x0, x1, x2 + cas x0, x1, x2, ra + )"; + + tasml::ErrorHandler reporter {vstl_self.name(), true}; + tasml::assemble(reporter, code); + + ASSERT(reporter.ok()); + + }; + TEST (tasml_emit_x86) { std::string code = R"( @@ -199,10 +209,13 @@ namespace test { util::TempFile embedded {".txt"}; embedded.write(embed); + // escape Windows path separators, replaces '\' with '\\' + auto escaped_path = std::regex_replace(embedded.path(),std::regex("\\\\"),"\\\\"); + std::string code = R"( section r export private begin: - embed ")" + embedded.path() + R"(" + embed ")" + escaped_path + R"(" export private end: )"; diff --git a/test/test.hpp b/test/test.hpp index 3150a97..962f676 100644 --- a/test/test.hpp +++ b/test/test.hpp @@ -1,57 +1,58 @@ #pragma once #include -#include -#include +#include namespace test { - inline std::string call_shell(std::string cmd, const std::string& input = "") { + inline void dump(asmio::SegmentedBuffer& buffer) { - // refers to the stdin/stdout of the child process - // [0] read, [1] write - int pipe_stdin[2]; - int pipe_stdout[2]; + if (buffer.elf_machine == asmio::ElfMachine::NONE) { + buffer.elf_machine = asmio::ElfMachine::NATIVE; + } - pipe2(pipe_stdin, 0); - pipe2(pipe_stdout, 0); + std::string extra_flags = ""; - pid_t pid = fork(); + if (buffer.elf_machine == asmio::ElfMachine::X86_64) { + extra_flags += "-Mintel "; + } - if (pid == 0) { - dup2(pipe_stdin[0], STDIN_FILENO); - dup2(pipe_stdout[1], STDOUT_FILENO); - dup2(pipe_stdout[1], STDERR_FILENO); + asmio::ObjectFile baked = asmio::to_elf(buffer, asmio::Label::UNSET).bake(); + asmio::util::TempFile temp {baked}; - // close the unused end - close(pipe_stdin[1]); - close(pipe_stdout[0]); + std::string out = asmio::call_shell("objdump --visualize-jumps -wxd " + extra_flags + temp.path()); + printf("%s\n", out.c_str()); - execl("/bin/sh", "sh", "-c", cmd.c_str(), nullptr); - exit(1); - } + printf("\nAuto-generated assertions:\n\n"); + printf("\tbuffer.link(0);\n"); - // close the unused end - close(pipe_stdin[0]); - close(pipe_stdout[1]); + int i = 0; - if (!input.empty()) { - write(pipe_stdin[1], input.data(), input.size()); - } + for (auto& segment : buffer.segments()) { + int count = segment.buffer.size(); + + if (count == 0) { + i ++; + continue; + } + + count --; + + printf("\tstd::vector s%d = {", i); - close(pipe_stdin[1]); + for (int j = 0; j <= count; j ++) { + printf("0x%02x", segment.buffer[j]); - std::string out; - char buf[256]; - ssize_t n; + if (count != j) { + printf(", "); + } + } - while ((n = read(pipe_stdout[0], buf, sizeof(buf))) > 0) { - out.append(buf, n); + printf("};\n"); + printf("\tCHECK(buffer.segments()[%d].buffer, s%d); // %s\n\n", i, i, segment.name.c_str()); + i ++; } - close(pipe_stdout[0]); - waitpid(pid, nullptr, 0); - return out; } -} \ No newline at end of file +} diff --git a/test/unit.cpp b/test/unit.cpp index cb34f9f..a1e9e89 100644 --- a/test/unit.cpp +++ b/test/unit.cpp @@ -1,14 +1,11 @@ -#define DEBUG_MODE false -#define VSTL_TEST_COUNT 3 -#define VSTL_PRINT_SKIP_REASON true -#define VSTL_SUBMODULE true - -#include -#include -#include -#include -#include -#include + +#include +#include +#include +#include +#include +#include +#include #include "vstl.hpp" @@ -71,6 +68,7 @@ namespace test { CHECK(util::min_signed_bytes(0x7FFF'FF01), 4); CHECK(util::min_signed_bytes(0x80), 2); CHECK(util::min_signed_bytes(0xFFFF), 4); + CHECK(util::min_signed_bytes(-1000), 2); }; @@ -483,4 +481,24 @@ namespace test { }; + TEST (util_call_shell) { + + std::string output = call_shell("echo hello", ""); + ASSERT(output.contains("hello")); + + }; + + TEST (util_string_trim) { + + CHECK(util::trim(" abc "), "abc"); + CHECK(util::trim("\tdef\n"), "def"); + CHECK(util::trim("\t \t ghi\r\n"), "ghi"); + + CHECK(util::trim("foo"), "foo"); + CHECK(util::trim(" bar"), "bar"); + CHECK(util::trim(" "), ""); + CHECK(util::trim(""), ""); + + }; + } \ No newline at end of file diff --git a/test/x86.cpp b/test/x86.cpp index 1073be1..0f4f0f7 100644 --- a/test/x86.cpp +++ b/test/x86.cpp @@ -1,21 +1,20 @@ -#define DEBUG_MODE false -#define VSTL_TEST_COUNT 3 -#define VSTL_PRINT_SKIP_REASON true -#define VSTL_PRINT_MODULES true - #include "vstl.hpp" -#include "asm/x86/writer.hpp" -#include +#include +#include // private libs #include -#include +#include +#include #include -#include +#include #include "test.hpp" +VCONF(repeats, 3); +VCONF(print_modules, true); + namespace test { using namespace asmio; @@ -473,6 +472,356 @@ namespace test { } + TEST (check_sse_cvtsi2ss) { + + SegmentedBuffer buffer; + BufferWriter writer {buffer}; + + writer.section(MemoryFlag::R | MemoryFlag::X); + writer.put_cvtsi2ss(XMM0, EAX); + writer.put_cvtsi2ss(XMM1, ref(RAX)); + + buffer.link(0); + std::vector s1 = {0xf3, 0x0f, 0x2a, 0xc0, 0xf3, 0x48, 0x0f, 0x2a, 0x08}; + CHECK(buffer.segments()[1].buffer, s1); // .text + + }; + + TEST (check_sse_movaps) { + + SegmentedBuffer buffer; + BufferWriter writer {buffer}; + + writer.section(MemoryFlag::R | MemoryFlag::X); + writer.put_movaps(XMM1, XMM10); + writer.put_movaps(XMM15, XMM10); + writer.put_movaps(XMM14, XMM1); + writer.put_movaps(XMM1, ref(RAX)); + writer.put_movaps(XMM2, ref(R12)); + writer.put_movaps(XMM13, ref(R12)); + writer.put_movaps(ref(RAX), XMM1); + writer.put_movaps(ref(R12), XMM1); + writer.put_movaps(ref(R12), XMM14); + + EXPECT_THROW(std::runtime_error) { + writer.put_movaps(ref(R12), RAX); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_movaps(XMM1, ref(XMM0)); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_movaps(XMM1, ref(R12)); + }; + + buffer.link(0); + std::vector s1 = {0x41, 0x0f, 0x28, 0xca, 0x45, 0x0f, 0x28, 0xfa, 0x44, 0x0f, 0x28, 0xf1, 0x0f, 0x28, 0x08, 0x41, 0x0f, 0x28, 0x14, 0x24, 0x45, 0x0f, 0x28, 0x2c, 0x24, 0x0f, 0x29, 0x08, 0x41, 0x0f, 0x29, 0x0c, 0x24, 0x45, 0x0f, 0x29, 0x34, 0x24}; + CHECK(buffer.segments()[1].buffer, s1); // .text + + }; + + TEST (check_sse_movhlps_movlhps_movhps_movlps) { + + SegmentedBuffer buffer; + BufferWriter writer {buffer}; + + writer.put_movhlps(XMM14, XMM1); + writer.put_movlhps(XMM14, XMM13); + writer.put_movhps(XMM0, ref(RSP)); + writer.put_movhps(ref(RBP), XMM15); + writer.put_movlps(ref(RCX * 2 + 0x100), XMM0); + + EXPECT_THROW(std::runtime_error) { + writer.put_movhlps(XMM14, RAX); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_movhps(XMM0, ref(R11)); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_movlps(ref(RCX), XMM0); + }; + + buffer.link(0); + std::vector s0 = {0x44, 0x0f, 0x12, 0xf1, 0x45, 0x0f, 0x16, 0xf5, 0x0f, 0x16, 0x04, 0x24, 0x44, 0x0f, 0x17, 0x7d, 0x00, 0x0f, 0x13, 0x04, 0x4d, 0x00, 0x01, 0x00, 0x00}; + CHECK(buffer.segments()[0].buffer, s0); // .rwx + + }; + + TEST (check_sse_movmskps) { + + SegmentedBuffer buffer; + BufferWriter writer {buffer}; + + writer.put_movmskps(RAX, XMM0); + writer.put_movmskps(RAX, XMM15); + writer.put_movmskps(ECX, XMM11); + + EXPECT_THROW(std::runtime_error) { + writer.put_movmskps(BX, XMM12); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_movmskps(DL, XMM14); + }; + + buffer.link(0); + std::vector s0 = {0x48, 0x0f, 0x50, 0xc0, 0x49, 0x0f, 0x50, 0xc7, 0x41, 0x0f, 0x50, 0xcb}; + CHECK(buffer.segments()[0].buffer, s0); // .rwx + + }; + + TEST (check_sse_op_packed) { + + SegmentedBuffer buffer; + BufferWriter writer {buffer}; + + writer.put_addps(XMM1, XMM1); + writer.put_divps(XMM11, XMM11); + writer.put_maxps(XMM1, XMM11); + writer.put_minps(XMM11, ref(RAX)); + writer.put_mulps(XMM1, ref(RCX)); + writer.put_rcpps(XMM11, ref(RBP)); + writer.put_rsqrtps(XMM11, XMM1); + writer.put_sqrtps(XMM1, ref(RCX * 8)); + writer.put_subps(XMM11, ref(RBP + 0x100)); + + EXPECT_THROW(std::runtime_error) { + writer.put_addps(XMM11, ref(RAX)); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_addps(RAX, ref(RAX)); + }; + + buffer.link(0); + std::vector s0 = {0x0f, 0x58, 0xc9, 0x45, 0x0f, 0x5e, 0xdb, 0x41, 0x0f, 0x5f, 0xcb, 0x44, 0x0f, 0x5d, 0x18, 0x0f, 0x59, 0x09, 0x44, 0x0f, 0x53, 0x5d, 0x00, 0x44, 0x0f, 0x52, 0xd9, 0x0f, 0x51, 0x0c, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x44, 0x0f, 0x5c, 0x9d, 0x00, 0x01, 0x00, 0x00}; + CHECK(buffer.segments()[0].buffer, s0); // .rwx + + } + + TEST (check_sse_op_bitwise) { + + SegmentedBuffer buffer; + BufferWriter writer {buffer}; + + writer.put_andnps(XMM1, XMM1); + writer.put_andps(XMM11, XMM11); + writer.put_orps(XMM1, XMM11); + writer.put_xorps(XMM11, ref(RAX)); + + buffer.link(0); + std::vector s0 = {0x0f, 0x55, 0xc9, 0x45, 0x0f, 0x54, 0xdb, 0x41, 0x0f, 0x56, 0xcb, 0x44, 0x0f, 0x57, 0x18}; + CHECK(buffer.segments()[0].buffer, s0); // .rwx + + } + + TEST (check_sse_op_scalar) { + + SegmentedBuffer buffer; + BufferWriter writer {buffer}; + + writer.put_addss(XMM1, XMM1); + writer.put_divss(XMM11, XMM11); + writer.put_maxss(XMM1, XMM11); + writer.put_minss(XMM11, ref(RAX)); + writer.put_mulss(XMM1, ref(RCX)); + writer.put_rcpss(XMM11, ref(RBP)); + writer.put_rsqrtss(XMM11, XMM1); + writer.put_sqrtss(XMM1, ref(RCX * 8)); + writer.put_subss(XMM11, ref(RBP + 0x100)); + + EXPECT_THROW(std::runtime_error) { + writer.put_addss(XMM11, ref(RAX)); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_addss(RAX, ref(RAX)); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_addss(XMM11, EAX); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_addss(XMM11, RAX); + }; + + buffer.link(0); + std::vector s0 = {0xf3, 0x0f, 0x58, 0xc9, 0xf3, 0x45, 0x0f, 0x5e, 0xdb, 0xf3, 0x41, 0x0f, 0x5f, 0xcb, 0xf3, 0x44, 0x0f, 0x5d, 0x18, 0xf3, 0x0f, 0x59, 0x09, 0xf3, 0x44, 0x0f, 0x53, 0x5d, 0x00, 0xf3, 0x44, 0x0f, 0x52, 0xd9, 0xf3, 0x0f, 0x51, 0x0c, 0xcd, 0x00, 0x00, 0x00, 0x00, 0xf3, 0x44, 0x0f, 0x5c, 0x9d, 0x00, 0x01, 0x00, 0x00}; + CHECK(buffer.segments()[0].buffer, s0); // .rwx + + } + + TEST (check_sse_cmpps_cmpss_comiss_ucomiss) { + + SegmentedBuffer buffer; + BufferWriter writer {buffer}; + + writer.put_cmpps(XMM1, XMM1, SimdCondition::EQ); + writer.put_cmpps(XMM2, XMM3, SimdCondition::LT); + writer.put_cmpss(XMM5, XMM8, SimdCondition::RD); + writer.put_cmpss(XMM11, XMM1, SimdCondition::NLT); + writer.put_comiss(XMM11, ref(RBP)); + writer.put_comiss(XMM11, XMM1); + writer.put_ucomiss(XMM11, XMM1); + writer.put_ucomiss(XMM11, ref(RAX)); + + buffer.link(0); + std::vector s0 = {0x0f, 0xc2, 0xc9, 0x00, 0x0f, 0xc2, 0xd3, 0x01, 0xf3, 0x41, 0x0f, 0xc2, 0xe8, 0x07, 0xf3, 0x44, 0x0f, 0xc2, 0xd9, 0x05, 0x44, 0x0f, 0x2f, 0x5d, 0x00, 0x44, 0x0f, 0x2f, 0xd9, 0x44, 0x0f, 0x2e, 0xd9, 0x44, 0x0f, 0x2e, 0x18}; + CHECK(buffer.segments()[0].buffer, s0); // .rwx + + }; + + TEST (check_sse_shufps_unpckhps_unpcklps) { + + SegmentedBuffer buffer; + BufferWriter writer {buffer}; + + writer.put_shufps(XMM11, ref(RAX + RCX), 0b1011); + writer.put_unpckhps(XMM11, ref(RAX)); + writer.put_unpcklps(XMM11, XMM1); + + buffer.link(0); + std::vector s0 = {0x44, 0x0f, 0xc6, 0x1c, 0x08, 0x0b, 0x44, 0x0f, 0x15, 0x18, 0x44, 0x0f, 0x14, 0xd9}; + CHECK(buffer.segments()[0].buffer, s0); // .rwx + + }; + + TEST (check_sse_cvtss2si_cvttss2si) { + + SegmentedBuffer buffer; + BufferWriter writer {buffer}; + + writer.put_cvtss2si(EAX, XMM1); + writer.put_cvtss2si(EAX, ref(RAX)); + writer.put_cvtss2si(EAX, ref(RAX)); + writer.put_cvtss2si(RAX, XMM1); + writer.put_cvtss2si(RAX, ref(RAX)); + writer.put_cvtss2si(RAX, ref(RAX)); + writer.put_cvtss2si(R11, XMM11); + writer.put_cvtss2si(R11, ref(R11)); + writer.put_cvtss2si(R11, ref(R11)); + writer.put_cvttss2si(R11, XMM11); + writer.put_cvttss2si(EAX, ref(R11)); + writer.put_cvttss2si(R11, ref(R11)); + + EXPECT_THROW(std::runtime_error) { + writer.put_cvtss2si(XMM11, XMM11); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_cvtss2si(R11, R11); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_cvtss2si(R11, ref(R11)); + }; + + buffer.link(0); + std::vector s0 = {0xf3, 0x0f, 0x2d, 0xc1, 0xf3, 0x0f, 0x2d, 0x00, 0xf3, 0x0f, 0x2d, 0x00, 0xf3, 0x48, 0x0f, 0x2d, 0xc1, 0xf3, 0x48, 0x0f, 0x2d, 0x00, 0xf3, 0x48, 0x0f, 0x2d, 0x00, 0xf3, 0x4d, 0x0f, 0x2d, 0xdb, 0xf3, 0x4d, 0x0f, 0x2d, 0x1b, 0xf3, 0x4d, 0x0f, 0x2d, 0x1b, 0xf3, 0x4d, 0x0f, 0x2c, 0xdb, 0xf3, 0x41, 0x0f, 0x2c, 0x03, 0xf3, 0x4d, 0x0f, 0x2c, 0x1b}; + CHECK(buffer.segments()[0].buffer, s0); // .rwx + + }; + + TEST (check_sse_ldmxcsr_stmxcsr) { + + SegmentedBuffer buffer; + BufferWriter writer {buffer}; + + writer.put_ldmxcsr(ref(EAX)); + writer.put_ldmxcsr(ref(ECX)); + writer.put_stmxcsr(ref(ESP)); + writer.put_stmxcsr(ref(ESP)); + + EXPECT_THROW(std::runtime_error) { + writer.put_ldmxcsr(RAX); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_stmxcsr(EAX); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_stmxcsr(XMM1); + }; + + buffer.link(0); + std::vector s0 = {0x67, 0x0f, 0xae, 0x10, 0x67, 0x0f, 0xae, 0x11, 0x67, 0x0f, 0xae, 0x1c, 0x24, 0x67, 0x0f, 0xae, 0x1c, 0x24}; + CHECK(buffer.segments()[0].buffer, s0); // .rwx + + }; + + TEST (check_sse_sfence_movntps) { + + SegmentedBuffer buffer; + BufferWriter writer {buffer}; + + writer.put_sfence(); + writer.put_movntps(ref(RAX), XMM1); + writer.put_movntps(ref(RAX), XMM11); + + EXPECT_THROW(std::runtime_error) { + writer.put_movntps(XMM1, XMM11); + }; + + buffer.link(0); + std::vector s0 = {0x0f, 0xae, 0xf8, 0x0f, 0x2b, 0x08, 0x44, 0x0f, 0x2b, 0x18}; + CHECK(buffer.segments()[0].buffer, s0); // .rwx + + }; + + TEST (check_sse_movss_movups) { + + SegmentedBuffer buffer; + BufferWriter writer {buffer}; + + writer.put_movups(XMM1, XMM2); + writer.put_movups(XMM1, ref(RAX)); + writer.put_movups(ref(RAX), XMM1); + + writer.put_movss(XMM1, XMM2); + writer.put_movss(XMM1, ref(RAX)); + writer.put_movss(ref(RAX), XMM1); + + EXPECT_THROW(std::runtime_error) { + writer.put_movups(XMM1, ref(RAX)); + }; + + EXPECT_THROW(std::runtime_error) { + writer.put_movss(XMM1, ref(RAX)); + }; + + buffer.link(0); + std::vector s0 = {0x0f, 0x10, 0xca, 0x0f, 0x10, 0x08, 0x0f, 0x11, 0x08, 0xf3, 0x0f, 0x10, 0xca, 0xf3, 0x0f, 0x10, 0x08, 0xf3, 0x0f, 0x11, 0x08}; + CHECK(buffer.segments()[0].buffer, s0); // .rwx + + }; + + TEST (check_imul) { + + SegmentedBuffer buffer; + BufferWriter writer {buffer}; + + // byte register not allowed + EXPECT_THROW(std::runtime_error) { + writer.put_imul(AL, AL, 1); + }; + + // can't encode 64 bit immediate + EXPECT_THROW(std::runtime_error) { + writer.put_imul(RAX, RAX, 10'000'000'000); + }; + + // can't encode 32 bit immediate for 16 operation + EXPECT_THROW(std::runtime_error) { + writer.put_imul(AX, AX, 100'000); + }; + + }; + /* * region Executable * Begin architecture depended tests for x86 @@ -1093,6 +1442,47 @@ namespace test { } + TEST(writer_exec_mul_imul_longer) { + + SegmentedBuffer segmented; + BufferWriter writer {segmented}; + + writer.label("imul_2"); + writer.put_mov(RAX, 2); + writer.put_imul(AX, AX, 1000); + writer.put_ret(); + + writer.label("imul_3"); + writer.put_mov(RAX, 3); + writer.put_imul(RAX, RAX, 1000); + writer.put_ret(); + + writer.label("imul_4"); + writer.put_mov(RAX, 4); + writer.put_imul(EAX, EAX, 1000000); + writer.put_ret(); + + writer.label("imul_5"); + writer.put_mov(RAX, 5); + writer.put_imul(RAX, RAX, 1000000000); + writer.put_ret(); + + writer.label("imul_6"); + writer.put_mov(EAX, 6); + writer.put_imul(AX, AX, -1000); + writer.put_movsx(RAX, AX); + writer.put_ret(); + + ExecutableBuffer buffer = to_executable(segmented); + + CHECK(buffer.call_i32("imul_2"), 2000); + CHECK(buffer.call_i32("imul_3"), 3000); + CHECK(buffer.call_u32("imul_4"), 4000000); + CHECK(buffer.call_u64("imul_5"), 5000000000); + CHECK(buffer.call_i32("imul_6"), -6000); + + } + TEST(writer_exec_div_idiv) { SegmentedBuffer segmented; @@ -1715,7 +2105,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32(), 1.0f); + CHECK(buffer.call_f80(), 1.0f); } @@ -1747,7 +2137,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32("main"), 3.0f); + CHECK(buffer.call_f80("main"), 3.0f); } @@ -1772,7 +2162,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32("main"), 2.5f); + CHECK(buffer.call_f80("main"), 2.5f); } @@ -1804,7 +2194,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32("main"), 10.75f); + CHECK(buffer.call_f80("main"), 10.75f); } @@ -1841,7 +2231,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32("main"), 12.5f); + CHECK(buffer.call_f80("main"), 12.5f); } @@ -1876,7 +2266,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32("main"), 6.0f); + CHECK(buffer.call_f80("main"), 6.0f); } @@ -1921,7 +2311,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32("main"), 4.5f); + CHECK(buffer.call_f80("main"), 4.5f); } @@ -1937,7 +2327,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32("main"), -1); + CHECK(buffer.call_f80("main"), -1); } @@ -1966,7 +2356,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32("main"), 20); + CHECK(buffer.call_f80("main"), 20); } @@ -1992,7 +2382,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32("main"), 7); + CHECK(buffer.call_f80("main"), 7); } @@ -2037,7 +2427,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32("main"), 1); + CHECK(buffer.call_f80("main"), 1); } @@ -2053,7 +2443,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32(), 1); + CHECK(buffer.call_f80(), 1); } @@ -2081,7 +2471,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32("main"), 6); + CHECK(buffer.call_f80("main"), 6); } @@ -2116,7 +2506,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32("init"), 1.0f); + CHECK(buffer.call_f80("init"), 1.0f); CHECK(buffer.call_i32("main"), 6); } @@ -2144,7 +2534,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32("init"), 1.0f); + CHECK(buffer.call_f80("init"), 1.0f); CHECK(buffer.call_i32("main"), 3); } @@ -2161,7 +2551,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32("main"), 3.0f); + CHECK(buffer.call_f80("main"), 3.0f); } @@ -2180,7 +2570,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32("main"), 4.0f); + CHECK(buffer.call_f80("main"), 4.0f); } @@ -2235,10 +2625,10 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.call_f32("set_a"), 2.0f); - CHECK(buffer.call_f32("set_b"), 1.0f); - CHECK(buffer.call_f32("set_c"), 0.0f); - CHECK(buffer.call_f32("ctrl_sum"), 11.0f); + CHECK(buffer.call_f80("set_a"), 2.0f); + CHECK(buffer.call_f80("set_b"), 1.0f); + CHECK(buffer.call_f80("set_c"), 0.0f); + CHECK(buffer.call_f80("ctrl_sum"), 11.0f); } @@ -2321,6 +2711,7 @@ namespace test { } TEST (writer_exec_int_0x80) { +#if _POSIX_C_SOURCE >= 200112L SegmentedBuffer segmented; BufferWriter writer {segmented}; @@ -2332,6 +2723,9 @@ namespace test { const uint32_t pid = to_executable(segmented).call_u32(); CHECK(pid, getpid()); +#else + SKIP ("_POSIX_C_SOURCE < 200112L") +#endif } TEST (writer_exec_long_back_jmp) { @@ -3290,10 +3684,14 @@ namespace test { segmented.elf_machine = ElfMachine::X86_64; ObjectFile file = to_elf(segmented, "_start").bake(); +#ifdef __linux__ RunResult result = file.execute("memfd-elf-1"); CHECK(result.type, RunStatus::SUCCESS); CHECK(result.status, 42); +#else + SKIP("ELF execution not supported in this environment") +#endif } @@ -3324,10 +3722,15 @@ namespace test { segmented.elf_machine = ElfMachine::X86_64; ObjectFile file = to_elf(segmented, "_start").bake(); + +#ifdef __linux__ RunResult result = file.execute("memfd-elf-1"); CHECK(result.type, RunStatus::SUCCESS); CHECK(result.status, 13); +#else + SKIP("ELF execution not supported in this environment") +#endif } @@ -3358,10 +3761,15 @@ namespace test { segmented.elf_machine = ElfMachine::X86_64; ObjectFile file = to_elf(segmented, "_start").bake(); + +#ifdef __linux__ RunResult result = file.execute("memfd-elf-1"); CHECK(result.type, RunStatus::SUCCESS); CHECK(result.status, 20); +#else + SKIP("ELF execution not supported in this environment") +#endif } @@ -3383,7 +3791,7 @@ namespace test { writer.put_ret(); ExecutableBuffer buffer = to_executable(segmented); - CHECK(buffer.size(), getpagesize() * 2); // expect there to be two pages + CHECK(buffer.size(), page_size() * 2); // expect there to be two pages CHECK(buffer.call_u32("read"), 42); EXPECT_SIGNAL(SIGSEGV) { @@ -3509,8 +3917,8 @@ namespace test { writer.label("simple_add"); writer.put_push(RBX); - writer.put_mov(RBX, ref(RAX)); - writer.put_add(RBX, ref(RAX + 8)); + writer.put_mov(RBX, ref(SCALL_REG)); + writer.put_add(RBX, ref(SCALL_REG + 8)); writer.put_mov(RAX, RBX); writer.put_pop(RBX); writer.put_ret(); @@ -3528,7 +3936,7 @@ namespace test { BufferWriter writer {segmented}; writer.label("main"); - writer.put_mov(RCX, ref(RAX)); + writer.put_mov(RCX, ref(SCALL_REG)); writer.put_mov(ref(RCX), 42); writer.put_ret(); @@ -3602,8 +4010,8 @@ namespace test { BufferWriter writer {segmented}; writer.label("add"); - writer.put_mov(RAX, ref(RDI + 0)); - writer.put_add(RAX, ref(RDI + 8)); + writer.put_mov(RAX, ref(SCALL_REG + 0)); + writer.put_add(RAX, ref(SCALL_REG + 8)); writer.put_ret(); // check if no segfault occures @@ -3654,8 +4062,8 @@ namespace test { // link with our object util::TempFile exec {".out"}; - std::string gcc_output = call_shell("gcc -z noexecstack -o " + exec.path() + " " + object.path() + " " + main_src.path() ); - CHECK(gcc_output, ""); + std::string gcc_output = call_shell("gcc -o " + exec.path() + " " + object.path() + " " + main_src.path() ); + ASSERT(!gcc_output.contains("error")); std::string exe_output = call_shell(exec.path()); CHECK(exe_output, "42"); @@ -3682,10 +4090,15 @@ namespace test { segmented.elf_machine = ElfMachine::X86_64; ObjectFile file = to_elf(segmented, "_start").bake(); + +#ifdef __linux__ RunResult result = file.execute("memfd-elf-1"); CHECK(result.type, RunStatus::SUCCESS); CHECK(result.status, 42); +#else + SKIP("ELF execution not supported in this environment") +#endif }; @@ -3734,7 +4147,7 @@ namespace test { volatile uint64_t test_value = 0; // imported from asmiov - uint64_t update_text(); + void update_text(); int main() { char* a = (char*) &test_value; @@ -3750,13 +4163,127 @@ namespace test { // link with our object util::TempFile exec {".out"}; - std::string gcc_output = call_shell("gcc -Wno-format -z noexecstack -o " + exec.path() + " " + object.path() + " " + main_src.path() ); - CHECK(gcc_output, ""); + std::string gcc_output = call_shell("gcc -Wno-format -o " + exec.path() + " " + object.path() + " " + main_src.path() ); + ASSERT(!gcc_output.contains("error")); std::string exe_output = call_shell(exec.path()); CHECK(exe_output, "HELLO FURRY!"); }; + TEST (exec_cpuid) { + + SegmentedBuffer buffer; + BufferWriter writer {buffer}; + + struct Result { + uint32_t eax; + uint32_t ebx; + uint32_t ecx; + uint32_t edx; + }; + + writer.label("cpuid"); + writer.put_push(RBX); + writer.put_push(RDI); + writer.put_mov(RDI, SCALL_REG); + writer.put_mov(RAX, ref(RDI)); // page + writer.put_cpuid(); + writer.put_mov(R8, ref(RDI + 8)); // result* + + writer.put_mov(ref(R8), EAX); // result->eax + writer.put_mov(ref(R8 + 4), EBX); // result->ebx + writer.put_add(R8, 8); + writer.put_mov(ref(R8), ECX); // result->ecx + writer.put_mov(ref(R8 + 4), EDX); // result->edx + writer.put_pop(RDI); + writer.put_pop(RBX); + writer.put_ret(); + + Result result {}; + + ExecutableBuffer exe = to_executable(buffer); + + // EAX:EDX:ECX should contain vendor name + // exe.scall("cpuid", uint64_t(0), &result); + + exe.scall("cpuid", uint64_t(1), &result); + + // check for support of some expected features + ASSERT_MSG(result.edx & (1 << 23), "MMX not supported or CPUID failed!"); + ASSERT_MSG(result.edx & (1 << 0), "FPU not supported or CPUID failed!"); + ASSERT_MSG(result.edx & (1 << 15), "CMOV not supported or CPUID failed!"); + ASSERT_MSG(result.edx & (1 << 25), "SSE not supported or CPUID failed!"); + ASSERT_MSG(result.edx & (1 << 26), "SSE2 not supported or CPUID failed!"); + ASSERT_MSG(result.ecx & (1 << 23), "POPCNT not supported or CPUID failed!"); + + }; + + TEST (exec_sse_generic_floats) { + + SegmentedBuffer buffer; + BufferWriter writer {buffer}; + + writer.section(MemoryFlag::R | MemoryFlag::W); + writer.label("three"); + writer.put_dword(3); + + writer.label("fraction"); + writer.put_dword_f(3.1415); + + writer.section(MemoryFlag::R | MemoryFlag::X); + writer.label("mult"); + writer.put_mov(EAX, 13); + writer.put_cvtsi2ss(XMM0, EAX); + writer.put_cvtsi2ss(XMM1, ref("three")); + writer.put_mulss(XMM0, XMM1); + writer.put_ret(); + + writer.label("pi"); + writer.put_movss(XMM0, ref("fraction")); + writer.put_ret(); + + ExecutableBuffer exe = to_executable(buffer); + + int res = (int) exe.call_f32("mult"); + CHECK(res, 39); + + float pi = exe.call_f32("pi"); + CHECK(pi, 3.1415); + + }; + + TEST (tasml_exec_string_prefix) { + + std::string code = R"( + lang x86 + + section r + value: + dword 3.5 + + section rx + syntax: + cmpps xmm11, xmmword [rax], nrd + cmpps xmm11, [rax], nrd + shufps xmm1, xmm15, 3 + + floats: + movss xmm1, dword [@value] + mulss xmm1, xmm1 + movss xmm0, xmm1 + ret + )"; + + SegmentedBuffer segmented = tasml::assemble(vstl_self.name(), code); + ExecutableBuffer buffer = to_executable(segmented); + + float f = buffer.call_f32("floats"); + + ASSERT(f > 12); + ASSERT(f < 13); + + }; + #endif ;} diff --git a/util/felix b/util/felix new file mode 100755 index 0000000..af45fd4 --- /dev/null +++ b/util/felix @@ -0,0 +1,52 @@ +#!/bin/env python3 +import requests +import sys +from bs4 import BeautifulSoup + +def to_markdown_table(data): + header = data[0] + rows = data[1:] + + md = "| " + " | ".join(map(str, header)) + " |\n" + md += "| " + " | ".join(["---"] * len(header)) + " |\n" + + for row in rows: + md += "| " + " | ".join(map(str, row)) + " |\n" + + return md + +if len(sys.argv) < 2: + print("Usage: felix-fetch ...") + exit(1) + +def print_x86(mnm): + url = 'https://www.felixcloutier.com/x86/' + mnm + res = requests.get(url) + + print("## " + mnm.upper()) + + if res.status_code != 200: + print("Mnemonic not found!\n") + return + + soup = BeautifulSoup(res.content, 'html.parser') + title = soup.title.string.split('—', 1)[1].strip(' \t\n\r') + + print(title) + + table = soup.find("table") # first table + rows = [] + + for row in table.find_all("tr"): + cells = row.find_all(["td", "th"]) + data = [cell.get_text(strip=True) for cell in cells] + + if data[0].startswith("VEX") or data[0].startswith("EVEX"): + continue + + rows.append(data) + + print(to_markdown_table(rows)) + +for arg in sys.argv[1:]: + print_x86(arg.lower())