
x86/x86-64 명령어 인코딩을 위한 헤더 전용 C++2x 컴파일 타임 어셈블러
컴파일 타임 x86/x86-64 명령어 인코딩을 위한 헤더 전용 C++20 라이브러리입니다.
이 프로젝트는 Boost Software License 1.0과 MIT License에 따라 이중 라이선스로 제공됩니다. 원하는 라이선스를 선택할 수 있습니다.
#include "static_asm.hpp"
using namespace static_asm::x86::registers;
using namespace static_asm::x86::instructions;
// Build machine code at compile time
constexpr auto code = core::assemble(
mov(rax, 0x12345678), // mov rax, imm32
add(rax, rcx), // add rax, rcx
xor_(r8, r8), // xor r8, r8
call(rax), // call rax
ret() // ret
);
// code is std::array<uint8_t, N> - fully constexpr!
// Register to register
add(rax, rbx); // 48 01 D8
sub(ecx, edx); // 29 D1
and_(r8, r9); // 4D 21 C8
or_(rsi, rdi); // 48 09 FE
xor_(eax, eax); // 31 C0 (common idiom to zero a register)
cmp(rax, rcx); // 48 39 C8
// Register with immediate
add(rax, 0x10); // 48 83 C0 10 (sign-extended imm8)
add(rax, 0x10000); // 48 05 00 00 01 00 (imm32)
sub(ecx, 100); // 83 E9 64
and_(rdx, 0xFF); // 48 83 E2 FF
// With memory operands
add(eax, dword_ptr(rbx)); // 03 03
add(rax, qword_ptr(rcx + 0x10)); // 48 03 41 10
sub(dword_ptr(rsp + 0x20), eax); // 29 44 24 20
// Register to register
mov(rax, rbx); // 48 89 D8
mov(eax, ecx); // 89 C8
mov(r8, r9); // 4D 89 C8
// Immediate to register
mov(rax, 0x12345678); // 48 C7 C0 78 56 34 12
mov(eax, 0xDEADBEEF); // B8 EF BE AD DE
// Memory operations
mov(rax, qword_ptr(rbx)); // 48 8B 03
mov(eax, dword_ptr(rcx + 0x10)); // 8B 41 10
mov(qword_ptr(rsp + 0x8), rax); // 48 89 44 24 08
mov(dword_ptr(rbp - 0x20), 0x100); // C7 45 E0 00 01 00 00
// Zero/sign extension
movzx(eax, bl); // 0F B6 C3 (zero-extend byte to dword)
movzx(rax, bx); // 48 0F B7 C3 (zero-extend word to qword)
movsx(eax, cl); // 0F BE C1 (sign-extend byte to dword)
movsx(rax, dx); // 48 0F BF C2 (sign-extend word to qword)
movsxd(rax, ecx); // 48 63 C1 (sign-extend dword to qword)
// Load effective address
lea(rax, qword_ptr(rbx + rcx * s4)); // 48 8D 04 8B
lea(rax, qword_ptr(rbx + rcx * s8 + 0x10)); // 48 8D 44 CB 10
// Exchange
xchg(rax, rbx); // 48 87 D8
// [base + index*scale]
mov(eax, dword_ptr(rbx + rcx * s1)); // 8B 04 0B
mov(eax, dword_ptr(rbx + rcx * s2)); // 8B 04 4B
mov(eax, dword_ptr(rbx + rcx * s4)); // 8B 04 8B
mov(eax, dword_ptr(rbx + rcx * s8)); // 8B 04 CB
// [base + index*scale + displacement]
mov(rax, qword_ptr(rbx + rcx * s4 + 0x10)); // 48 8B 44 8B 10
mov(rax, qword_ptr(r12 + r13 * s8 + 0x1000)); // 4B 8B 84 EC 00 10 00 00
// Store to SIB address
mov(dword_ptr(rax + rdx * s4), ecx); // 89 0C 90
mov(qword_ptr(rbx + rsi * s8 + 0x20), rax); // 48 89 44 F3 20
// LEA with SIB (useful for address calculations)
lea(rax, qword_ptr(rbx + rcx * s4)); // 48 8D 04 8B
lea(rax, qword_ptr(rdi + rsi * s8 + 0x100)); // 48 8D 84 F7 00 01 00 00
// Shift by 1
shl(eax, 1); // D1 E0
shr(rax, 1); // 48 D1 E8
sar(ecx, 1); // D1 F9
// Shift by immediate
shl(eax, 4); // C1 E0 04
shr(rax, 8); // 48 C1 E8 08
sar(rdx, 16); // 48 C1 FA 10
// Shift by CL register
shl(eax, cl); // D3 E0
shr(rax, cl); // 48 D3 E8
// Rotate
rol(eax, 1); // D1 C0
ror(rax, 8); // 48 C1 C8 08
rcl(ecx, cl); // D3 D1
rcr(rdx, 1); // 48 D1 DA
// Single operand (result in rdx:rax)
mul(rbx); // 48 F7 E3 (unsigned: rdx:rax = rax * rbx)
imul(rcx); // 48 F7 E9 (signed: rdx:rax = rax * rcx)
div(rbx); // 48 F7 F3 (unsigned: rax = rdx:rax / rbx, rdx = remainder)
idiv(rcx); // 48 F7 F9 (signed division)
// Two-operand IMUL (dest = dest * src)
imul(rax, rbx); // 48 0F AF C3
imul(ecx, edx); // 0F AF CA
// Three-operand IMUL (dest = src * imm)
imul(rax, rbx, 10); // 48 6B C3 0A
imul(ecx, edx, 1000); // 69 CA E8 03 00 00
// Unconditional jumps
jmp(0x10); // EB 10 (short, 8-bit offset)
jmp(0x1000); // E9 00 10 00 00 (near, 32-bit offset)
jmp(rax); // FF E0 (indirect)
jmp(here); // EB FE (jmp $, infinite loop)
// Conditional jumps (8-bit offset)
jz(0x10); // 74 10
jnz(0x20); // 75 20
jb(0x08); // 72 08 (below/carry)
jae(0x08); // 73 08 (above or equal/no carry)
jl(0x10); // 7C 10 (less than, signed)
jge(0x10); // 7D 10 (greater or equal, signed)
// Conditional jumps (32-bit offset for longer branches)
jz_near(0x10000); // 0F 84 00 00 01 00
jnz_near(0x20000); // 0F 85 00 00 02 00
// Call and return
call(rax); // FF D0 (indirect call)
call(0x100); // E8 00 01 00 00 (relative call)
ret(); // C3
ret(0x10); // C2 10 00 (return and pop 16 bytes)
// Move if condition is true (no branch penalty!)
cmovz(rax, rbx); // 48 0F 44 C3 (move if zero)
cmovnz(eax, ecx); // 0F 45 C1 (move if not zero)
cmovl(rax, rdx); // 48 0F 4C C2 (move if less, signed)
cmovge(ecx, esi); // 0F 4D CE (move if greater or equal, signed)
cmovb(rax, rbx); // 48 0F 42 C3 (move if below, unsigned)
cmovae(edx, edi); // 0F 43 D7 (move if above or equal, unsigned)
// With memory source
cmovz(rax, qword_ptr(rbx)); // 48 0F 44 03
cmovnz(eax, dword_ptr(rcx + 0x10)); // 0F 45 41 10
// Bit test
bt(eax, 5); // 0F BA E0 05
bt(rax, rbx); // 48 0F A3 D8
// Bit test and set/reset/complement
bts(eax, 10); // 0F BA E8 0A (test and set)
btr(rax, rcx); // 48 0F B3 C8 (test and reset)
btc(edx, 3); // 0F BA FA 03 (test and complement)
// Bit scan
bsf(eax, ecx); // 0F BC C1 (scan forward for first 1)
bsr(rax, rbx); // 48 0F BD C3 (scan reverse for first 1)
// Population count and leading/trailing zeros
popcnt(eax, ecx); // F3 0F B8 C1
lzcnt(rax, rbx); // F3 48 0F BD C3
tzcnt(eax, edx); // F3 0F BC C2
// Byte swap
bswap(eax); // 0F C8 (reverse byte order)
bswap(rax); // 48 0F C8
// Basic string ops (operate on [rsi] and/or [rdi])
movsb(); // A4 (move byte [rsi] -> [rdi])
movsw(); // 66 A5
movsd(); // A5
movsq(); // 48 A5
cmpsb(); // A6 (compare [rsi] with [rdi])
stosb(); // AA (store al -> [rdi])
lodsb(); // AC (load [rsi] -> al)
scasb(); // AE (compare al with [rdi])
// With REP prefix (repeat rcx times)
rep_movsb(); // F3 A4 (memcpy)
rep_movsq(); // F3 48 A5 (fast memcpy, 8 bytes at a time)
rep_stosb(); // F3 AA (memset)
rep_stosq(); // F3 48 AB
// With REPE/REPNE (repeat while equal/not equal)
repe_cmpsb(); // F3 A6 (compare strings until mismatch)
repne_scasb(); // F2 AE (scan for byte in string)
push(rax); // 50
push(rbx); // 53
push(r8); // 41 50
push(0x10); // 6A 10 (push imm8)
push(0x1000); // 68 00 10 00 00 (push imm32)
pop(rax); // 58
pop(rbx); // 5B
pop(r15); // 41 5F
// System calls
syscall_(); // 0F 05 (64-bit syscall)
sysenter(); // 0F 34
sysexit(); // 0F 35
// Interrupts
int3(); // CC (breakpoint)
int_(0x80); // CD 80 (Linux 32-bit syscall)
int_(0x21); // CD 21 (DOS interrupt)
// CPU info
cpuid(); // 0F A2
rdtsc(); // 0F 31
rdtscp(); // 0F 01 F9
// Privilege
cli(); // FA (clear interrupts)
sti(); // FB (set interrupts)
hlt(); // F4 (halt)
// Interrupt return
iret(); // CF (16-bit)
iretd(); // CF (32-bit)
iretq(); // 48 CF (64-bit)
core::assemble()을 사용하여 명령어 바이트 배열을 연결합니다:
constexpr auto prologue = core::assemble(
push(rbp),
mov(rbp, rsp),
sub(rsp, 0x20)
);
constexpr auto epilogue = core::assemble(
add(rsp, 0x20),
pop(rbp),
ret()
);
// Combine them
constexpr auto full_function = core::assemble(prologue, epilogue);
CMakeLists.txt에 추가:
include(FetchContent)
FetchContent_Declare(
static_asm
GIT_REPOSITORY https://github.com/mahmoudimus/static_asm.git
GIT_TAG v1.0.0 # or specific commit
)
FetchContent_MakeAvailable(static_asm)
target_link_libraries(your_target PRIVATE static_asm::static_asm)
클론하거나 git submodule로 추가:
git submodule add https://github.com/mahmoudimus/static_asm.git external/static_asm
그런 다음 CMakeLists.txt에:
add_subdirectory(external/static_asm)
target_link_libraries(your_target PRIVATE static_asm::static_asm)
add_subdirectory 또는 FetchContent로 포함 시 프로젝트에는 static_asm::static_asm 인터페이스 라이브러리 타겟만 추가됩니다. 명시적으로 -DSTATIC_ASM_BUILD_TESTS=ON을 설정하지 않으면 테스트와 예제는 빌드되지 않습니다.
릴리스 페이지에서 static_asm.hpp를 다운로드하여 직접 포함:
#include "static_asm.hpp"
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --install build --prefix /usr/local
그런 다음 find_package 사용:
find_package(static_asm REQUIRED)
target_link_libraries(your_target PRIVATE static_asm::static_asm)
# Configure
cmake -B build -DCMAKE_BUILD_TYPE=Release
# Build
cmake --build build
# Run tests
ctest --test-dir build --output-on-failure
# Build with examples (Clang only, uses inline assembly)
cmake -B build -DCMAKE_BUILD_TYPE=Release -DSTATIC_ASM_BUILD_EXAMPLES=ON
지원 플랫폼:
참고: core::emit() 인라인 어셈블리 기능은 Clang과 -O2 최적화가 필요합니다.
피연산자 지원:
[base + index*scale + disp] (스케일 팩터 1, 2, 4, 8)참고: 아직 SIMD/AVX 확장은 지원되지 않습니다.
이 프로젝트는 Python 도구(코드 생성, 단일 헤더 통합)를 위해 uv를 사용합니다.
# Install uv (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Verify installation
uv --version
# All Python scripts can be run directly with uv (dependencies are auto-managed)
uv run scripts/gen_from_x86ref.py --help
uv run scripts/amalgamate.sh
필수 도구:
개발용 선택 도구:
| 도구 | 용도 | 설치 방법 |
|---|---|---|
| clang-format | 코드 포맷팅 | LLVM 또는 시스템 패키지 |
| clang-tidy | 정적 분석 | LLVM 또는 시스템 패키지 |
이 프로젝트에는 x86reference XML 데이터베이스를 구문 분석하는 생성기가 포함되어 있습니다:
# Show instruction database summary
uv run scripts/gen_from_x86ref.py
# Show details for a specific instruction
uv run scripts/gen_from_x86ref.py -i lea
uv run scripts/gen_from_x86ref.py -i imul
# Generate instruction database files
uv run scripts/gen_from_x86ref.py --generate-db
# Generate exhaustive test file
uv run scripts/gen_from_x86ref.py --generate-tests
instdb, prefix_db, prefix_0fdb 배열)에 추가encoder.hpp에서 인코더를 코딩하거나 기존 인코더 확장임의의 다중 파일 C++ 라이브러리를 깔끔하고 헤더 전용 버전으로 안정적으로 변환하는 자동 도구는 수동 준비 없이 존재하지 않습니다. 다음 기법들은 라이브러리가 정확성, 유지보수성, 표준 준수를 유지하면서 단일 헤더 파일로 성공적으로 통합될 수 있도록 보장합니다.
참고: 이 프로젝트는 통합을 위해 quom을, 이러한 규칙의 자동 적용을 위해
google-build-using-namespace검사를 수행하는 clang-tidy를 사용합니다. quom은 처리 능력을 통해 기법 #4(인라인 마커)를 부분적으로 처리합니다.
using namespace 사용 피하기.cpp 파일에서 파일 범위의 using namespace는 나중에 헤더에 포함될 때 위험합니다. 이는 헤더를 포함하는 모든 번역 단위에 전역 네임스페이스를 오염시키기 때문입니다.
대신 권장되는 패턴:
// Preferred: wrap implementation in namespace
namespace MyLib {
Foo::Foo() {
// ...
}
}
또는
// Explicit qualification (more verbose but very clear)
MyLib::Foo::Foo() {
// ...
}
공개 API는 주 네임스페이스에 있어야 합니다. 최종 사용자를 위한 것이 아닌 모든 것은 detail 또는 impl과 같은 중첩 네임스페이스에 숨겨야 합니다.
일반적인 관례:
namespace MyLib {
namespace detail { // very widely used
// internal classes, functions, etc.
}
}
또는
namespace MyLib::impl { // shorter, also common
// internal implementation details
}
C++17 이상에서는 인라인 중첩 네임스페이스 정의를 지원하며, 이는 더 깔끔합니다:
namespace MyLib::detail {
class InternalHelper { /* ... */ };
}
static inline 클래스 멤버로 변환.cpp 파일에 정의된 파일 범위 static 변수는 헤더 전용 세계에서 문제가 됩니다(여러 정의, ODR 위반).
최신(C++17+) 해결책:
// Before (in .cpp)
namespace MyLib {
static int s_counter = 0;
int next_id() {
return ++s_counter;
}
}
// After (safe for header)
namespace MyLib::detail {
struct Globals {
static inline int counter = 0;
};
}
inline int MyLib::next_id() {
return ++detail::Globals::counter;
}
static inline 변수는 여러 번 포함되더라도 단일 정의가 보장됩니다.
inline으로 표시헤더에 본문이 나타나지만(클래스 정의 내부가 아닌) 함수, 멤버 함수, 생성자, 소멸자는 ODR(One Definition Rule) 위반을 피하기 위해 반드시 inline으로 표시해야 합니다.
많은 통합 스크립트는 순전히 텍스트 기반이며 C++ 의미를 구문 분석하지 않기 때문에, 개발 중에는 자리 표시자 매크로(예: inline_t)를 사용하는 것이 일반적인 관례입니다:
// MyLib.h (or common header)
#define inline_t /* empty during normal builds */
// MyLib.cpp (during development)
namespace MyLib::detail {
inline_t void Helper::do_work() {
// implementation
}
}
통합 과정에서 도구가 inline_t를 inline으로 대체합니다:
// After amalgamation / transformation
inline void MyLib::detail::Helper::do_work() {
// ...
}
원하는 매크로 이름을 선택할 수 있으며(예: MYLIB_INLINE, INLINE_IMP 등) 통합 스크립트에 맞게 구성할 수 있습니다.
참고: quom과 같은 도구는 C++ 포함 의미를 이해하고 통합 과정에서 함수 정의를 적절히 처리함으로써 많은 경우 수동
inline마커의 필요성을 줄일 수 있습니다.
using namespace …를 절대 쓰지 마세요.detail / impl)에 넣으세요.static 데이터를 구조체/클래스의 static inline 멤버로 대체하세요.이 네 가지 관행을 따르면 순수 텍스트 기반 통합 도구를 사용하더라도 단일 헤더 배포로의 전환이 훨씬 매끄럽고 오류 가능성이 훨씬 낮아집니다.
이 프로젝트는 Midi12의 cx_assembler를 기반으로 합니다. 원본 라이브러리는 C++에서 컴파일 타임 x86 어셈블리 인코딩의 기초를 제공했습니다.
| 카테고리 | 명령어 |
|---|
| ALU | ADD, ADC, SUB, SBB, AND, OR, XOR, CMP, TEST |
| 단항 | INC, DEC, NEG, NOT |
| 곱셈/나눗셈 | MUL, IMUL (1/2/3 피연산자 형태), DIV, IDIV |
| 데이터 이동 | MOV, MOVABS, MOVZX, MOVSX, MOVSXD, LEA, XCHG, PUSH, POP |
| 시프트/회전 | SHL, SHR, SAL, SAR, ROL, ROR, RCL, RCR |
| 제어 흐름 | JMP, CALL, RET, RETF |
| 조건부 점프 | JZ/JE, JNZ/JNE, JB/JC, JNB/JNC, JBE/JNA, JNBE/JA, JL, JNL, JLE, JNLE, JO, JNO, JS, JNS, JP, JNP (8비트 및 32비트 오프셋) |
| 조건부 이동 | CMOVA, CMOVAE, CMOVB, CMOVBE, CMOVE, CMOVG, CMOVGE, CMOVL, CMOVLE, CMOVNE, CMOVNO, CMOVNP, CMOVNS, CMOVO, CMOVP, CMOVS |
| 비트 연산 | BT, BTC, BTR, BTS |
| 비트 스캔/카운트 | BSF, BSR, POPCNT, LZCNT, TZCNT, BSWAP |
| 문자열 연산 | MOVSB/W/D/Q, CMPSB/W/D/Q, LODSB/W/D/Q, STOSB/W/D/Q, SCASB/W/D/Q (REP/REPE/REPNE 접두사 포함) |
| 시스템 | SYSCALL, SYSENTER, SYSEXIT, INT, INT3, IRET/D/Q, CLI, STI, HLT, CPUID, RDTSC, RDTSCP |
| 기타 | NOP, UD2 |
| 도구 | 용도 | 설치 방법 |
|---|
| uv | Python 패키지/프로젝트 관리자 | curl -LsSf https://astral.sh/uv/install.sh | sh |
| quom | 단일 헤더 통합 | uv tool install quom |
| CMake 3.19+ | 빌드 시스템 | cmake.org |
| C++20 컴파일러 | GCC 11+, Clang 14+, MSVC 2022+ | - |