
Montador de cabeçalho único em C++2x em tempo de compilação para codificação de instruções x86/x86-64
Uma biblioteca C++20 apenas de cabeçalho para codificação de instruções x86/x86-64 em tempo de compilação.
Este projeto é licenciado duplamente sob a Boost Software License 1.0 e a MIT License. Você pode escolher qualquer uma das licenças.
#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)
Use core::assemble() para concatenar arrays de bytes de instruções:
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);
Adicione ao seu CMakeLists.txt:
include(FetchContent)
FetchContent_Declare(
static_asm
GIT_REPOSITORY https://github.com/mahmoudimus/static_asm.git
GIT_TAG v1.0.0 # ou commit específico
)
FetchContent_MakeAvailable(static_asm)
target_link_libraries(seu_projeto PRIVATE static_asm::static_asm)
Clone ou adicione como submodulo git:
git submodule add https://github.com/mahmoudimus/static_asm.git external/static_asm
Então no seu CMakeLists.txt:
add_subdirectory(external/static_asm)
target_link_libraries(seu_projeto PRIVATE static_asm::static_asm)
Quando incluído via add_subdirectory ou FetchContent, apenas o alvo de biblioteca de interface static_asm::static_asm é adicionado ao seu projeto. Testes e exemplos não são compilados a menos que explicitamente habilitados com -DSTATIC_ASM_BUILD_TESTS=ON.
Baixe static_asm.hpp da página de releases e inclua diretamente:
#include "static_asm.hpp"
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --install build --prefix /usr/local
Então use find_package:
find_package(static_asm REQUIRED)
target_link_libraries(seu_projeto PRIVATE static_asm::static_asm)
# Configurar
cmake -B build -DCMAKE_BUILD_TYPE=Release
# Compilar
cmake --build build
# Executar testes
ctest --test-dir build --output-on-failure
# Compilar com exemplos (apenas Clang, usa assembly inline)
cmake -B build -DCMAKE_BUILD_TYPE=Release -DSTATIC_ASM_BUILD_EXAMPLES=ON
Suporte de plataforma:
Nota: O recurso de assembly inline core::emit() requer Clang com otimização -O2.
Suporte a operandos:
[base + index*scale + disp] com fatores de escala 1, 2, 4, 8Nota: Nenhuma extensão SIMD/AVX ainda.
Este projeto usa uv para ferramentas Python (geração de código, amalgamação de cabeçalho único).
# Instalar uv (se ainda não estiver instalado)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Verificar instalação
uv --version
# Todos os scripts Python podem ser executados diretamente com uv (dependências são gerenciadas automaticamente)
uv run scripts/gen_from_x86ref.py --help
uv run scripts/amalgamate.sh
Ferramentas necessárias:
Ferramentas opcionais para desenvolvimento:
| Ferramenta | Finalidade | Instalação |
|---|---|---|
| clang-format | Formatação de código | Via LLVM ou pacote do sistema |
| clang-tidy | Análise estática | Via LLVM ou pacote do sistema |
O projeto inclui um gerador que analisa o banco de dados XML do x86reference:
# Mostrar resumo do banco de dados de instruções
uv run scripts/gen_from_x86ref.py
# Mostrar detalhes para uma instrução específica
uv run scripts/gen_from_x86ref.py -i lea
uv run scripts/gen_from_x86ref.py -i imul
# Gerar arquivos de banco de dados de instruções
uv run scripts/gen_from_x86ref.py --generate-db
# Gerar arquivo de teste exaustivo
uv run scripts/gen_from_x86ref.py --generate-tests
instdb, prefix_db, prefix_0fdb)encoder.hpp ou estenda um codificador existenteNenhuma ferramenta automática pode converter de forma confiável uma biblioteca C++ arbitrária de múltiplos arquivos em uma versão limpa e apenas de cabeçalho sem alguma preparação manual. As técnicas a seguir ajudam a garantir que sua biblioteca possa ser amalgamada com sucesso em um único arquivo de cabeçalho, permanecendo correta, sustentável e em conformidade com os padrões.
Nota: Este projeto usa quom para amalgamação e clang-tidy com a verificação
google-build-using-namespacepara automatizar a aplicação dessas regras. O quom também lida parcialmente com a técnica #4 (marcadores inline) através de suas capacidades de processamento.
using namespace em arquivos fonteusing namespace no escopo do arquivo em arquivos .cpp é perigoso quando esses arquivos são posteriormente incluídos em um cabeçalho — ele polui o namespace global para toda unidade de tradução que inclui seu cabeçalho.
Padrões recomendados em vez disso:
// Preferido: envolver a implementação no namespace
namespace MyLib {
Foo::Foo() {
// ...
}
}
ou
// Qualificação explícita (mais verbosa, mas muito clara)
MyLib::Foo::Foo() {
// ...
}
APIs públicas devem viver no namespace principal. Tudo o que não é destinado a usuários finais deve ser escondido em um namespace aninhado como detail ou impl.
Convenções comuns:
namespace MyLib {
namespace detail { // muito usado
// classes internas, funções, etc.
}
}
ou
namespace MyLib::impl { // mais curto, também comum
// detalhes internos da implementação
}
C++17 e posteriores suportam definições de namespace aninhado inline, que são mais limpas:
namespace MyLib::detail {
class InternalHelper { /* ... */ };
}
static inlineVariáveis static de escopo de arquivo definidas em arquivos .cpp tornam-se problemáticas em um mundo apenas de cabeçalho (definições múltiplas, violações de ODR).
Solução moderna (C++17+):
// Antes (em .cpp)
namespace MyLib {
static int s_counter = 0;
int next_id() {
return ++s_counter;
}
}
// Depois (seguro para cabeçalho)
namespace MyLib::detail {
struct Globals {
static inline int counter = 0;
};
}
inline int MyLib::next_id() {
return ++detail::Globals::counter;
}
A variável static inline tem garantia de ter uma única definição mesmo quando incluída múltiplas vezes.
inlineQualquer função, função membro, construtor ou destrutor cujo corpo aparece no cabeçalho (mas não dentro da definição da classe) deve ser marcado como inline para evitar violações da Regra de Definição Única (ODR).
Como muitos scripts de amalgamação são puramente textuais e não analisam a semântica de C++, uma convenção comum é usar uma macro placeholder (ex.: inline_t) durante o desenvolvimento:
// MyLib.h (ou cabeçalho comum)
#define inline_t /* vazio durante builds normais */
// MyLib.cpp (durante desenvolvimento)
namespace MyLib::detail {
inline_t void Helper::do_work() {
// implementação
}
}
Durante a amalgamação, a ferramenta substitui inline_t por inline:
// Após amalgamação / transformação
inline void MyLib::detail::Helper::do_work() {
// ...
}
Você pode escolher qualquer nome de macro que preferir (ex.: MYLIB_INLINE, INLINE_IMP, etc.) e configurar seu script de amalgamação de acordo.
Nota: Ferramentas como quom podem automatizar parcialmente isso, entendendo a semântica de inclusão de C++ e lidando corretamente com definições de funções durante a amalgamação, reduzindo a necessidade de marcadores
inlinemanuais em muitos casos.
using namespace … em escopo de namespace/arquivo em arquivos de implementação.detail / impl).static de escopo de arquivo por membros static inline de uma struct/classe.inline (substituída durante a amalgamação).Seguir essas quatro práticas torna a transição para uma distribuição de cabeçalho único muito mais suave e com muito menos erros — mesmo ao usar ferramentas de amalgamação puramente textuais.
Este projeto é baseado em cx_assembler por Midi12. A biblioteca original forneceu a base para a codificação de assembly x86 em tempo de compilação em C++.
| Categoria | Instruções |
|---|
| ALU | ADD, ADC, SUB, SBB, AND, OR, XOR, CMP, TEST |
| Unárias | INC, DEC, NEG, NOT |
| Multiplicação/Divisão | MUL, IMUL (formas 1/2/3 operandos), DIV, IDIV |
| Movimentação de Dados | MOV, MOVABS, MOVZX, MOVSX, MOVSXD, LEA, XCHG, PUSH, POP |
| Deslocamento/Rotação | SHL, SHR, SAL, SAR, ROL, ROR, RCL, RCR |
| Fluxo de Controle | JMP, CALL, RET, RETF |
| Saltos Condicionais | JZ/JE, JNZ/JNE, JB/JC, JNB/JNC, JBE/JNA, JNBE/JA, JL, JNL, JLE, JNLE, JO, JNO, JS, JNS, JP, JNP (offsets de 8 e 32 bits) |
| Movimentos Condicionais | CMOVA, CMOVAE, CMOVB, CMOVBE, CMOVE, CMOVG, CMOVGE, CMOVL, CMOVLE, CMOVNE, CMOVNO, CMOVNP, CMOVNS, CMOVO, CMOVP, CMOVS |
| Operações com Bits | BT, BTC, BTR, BTS |
| Varredura/Contagem de Bits | BSF, BSR, POPCNT, LZCNT, TZCNT, BSWAP |
| Operações com Strings | MOVSB/W/D/Q, CMPSB/W/D/Q, LODSB/W/D/Q, STOSB/W/D/Q, SCASB/W/D/Q (com prefixos REP/REPE/REPNE) |
| Sistema | SYSCALL, SYSENTER, SYSEXIT, INT, INT3, IRET/D/Q, CLI, STI, HLT, CPUID, RDTSC, RDTSCP |
| Diversos | NOP, UD2 |
| Ferramenta | Finalidade | Instalação |
|---|
| uv | Gerenciador de pacotes/projetos Python | curl -LsSf https://astral.sh/uv/install.sh | sh |
| quom | Amalgamação de cabeçalho único | uv tool install quom |
| CMake 3.19+ | Sistema de build | cmake.org |
| Compilador C++20 | GCC 11+, Clang 14+, MSVC 2022+ | - |