
Assembler C++2x solo header per la codifica di istruzioni x86/x86-64 a tempo di compilazione
Una libreria C++20 solo header per la codifica di istruzioni x86/x86-64 in fase di compilazione.
Questo progetto è concesso in doppia licenza sotto la Boost Software License 1.0 e la MIT License. Puoi scegliere una delle due licenze.
#include "static_asm.hpp"
using namespace static_asm::x86::registers;
using namespace static_asm::x86::instructions;
// Codice macchina compilato a tempo di compilazione
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 è std::array<uint8_t, N> - completamente constexpr!
// Registro a registro
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 (idioma comune per azzerare un registro)
cmp(rax, rcx); // 48 39 C8
// Registro con immediato
add(rax, 0x10); // 48 83 C0 10 (imm8 esteso con segno)
add(rax, 0x10000); // 48 05 00 00 01 00 (imm32)
sub(ecx, 100); // 83 E9 64
and_(rdx, 0xFF); // 48 83 E2 FF
// Con operandi di memoria
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
// Registro a registro
mov(rax, rbx); // 48 89 D8
mov(eax, ecx); // 89 C8
mov(r8, r9); // 4D 89 C8
// Immediato a registro
mov(rax, 0x12345678); // 48 C7 C0 78 56 34 12
mov(eax, 0xDEADBEEF); // B8 EF BE AD DE
// Operazioni di memoria
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
// Estensione zero / segno
movzx(eax, bl); // 0F B6 C3 (estensione zero da byte a dword)
movzx(rax, bx); // 48 0F B7 C3 (estensione zero da word a qword)
movsx(eax, cl); // 0F BE C1 (estensione segno da byte a dword)
movsx(rax, dx); // 48 0F BF C2 (estensione segno da word a qword)
movsxd(rax, ecx); // 48 63 C1 (estensione segno da dword a qword)
// Carica indirizzo effettivo
lea(rax, qword_ptr(rbx + rcx * s4)); // 48 8D 04 8B
lea(rax, qword_ptr(rbx + rcx * s8 + 0x10)); // 48 8D 44 CB 10
// Scambio
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
// Memorizza su indirizzo SIB
mov(dword_ptr(rax + rdx * s4), ecx); // 89 0C 90
mov(qword_ptr(rbx + rsi * s8 + 0x20), rax); // 48 89 44 F3 20
// LEA con SIB (utile per calcoli di indirizzi)
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 di 1
shl(eax, 1); // D1 E0
shr(rax, 1); // 48 D1 E8
sar(ecx, 1); // D1 F9
// Shift con immediato
shl(eax, 4); // C1 E0 04
shr(rax, 8); // 48 C1 E8 08
sar(rdx, 16); // 48 C1 FA 10
// Shift con registro CL
shl(eax, cl); // D3 E0
shr(rax, cl); // 48 D3 E8
// Rotazione
rol(eax, 1); // D1 C0
ror(rax, 8); // 48 C1 C8 08
rcl(ecx, cl); // D3 D1
rcr(rdx, 1); // 48 D1 DA
// Operando singolo (risultato 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 = resto)
idiv(rcx); // 48 F7 F9 (divisione signed)
// IMUL a due operandi (dest = dest * src)
imul(rax, rbx); // 48 0F AF C3
imul(ecx, edx); // 0F AF CA
// IMUL a tre operandi (dest = src * imm)
imul(rax, rbx, 10); // 48 6B C3 0A
imul(ecx, edx, 1000); // 69 CA E8 03 00 00
// Salti incondizionati
jmp(0x10); // EB 10 (short, offset a 8 bit)
jmp(0x1000); // E9 00 10 00 00 (near, offset a 32 bit)
jmp(rax); // FF E0 (indiretto)
jmp(here); // EB FE (jmp $, ciclo infinito)
// Salti condizionati (offset a 8 bit)
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)
// Salti condizionati (offset a 32 bit per rami più lunghi)
jz_near(0x10000); // 0F 84 00 00 01 00
jnz_near(0x20000); // 0F 85 00 00 02 00
// Call e return
call(rax); // FF D0 (chiamata indiretta)
call(0x100); // E8 00 01 00 00 (chiamata relativa)
ret(); // C3
ret(0x10); // C2 10 00 (return e pop di 16 byte)
// Muovi se la condizione è vera (nessuna penalità di branch!)
cmovz(rax, rbx); // 48 0F 44 C3 (muovi se zero)
cmovnz(eax, ecx); // 0F 45 C1 (muovi se non zero)
cmovl(rax, rdx); // 48 0F 4C C2 (muovi se minore, signed)
cmovge(ecx, esi); // 0F 4D CE (muovi se maggiore o uguale, signed)
cmovb(rax, rbx); // 48 0F 42 C3 (muovi se below, unsigned)
cmovae(edx, edi); // 0F 43 D7 (muovi se above o uguale, unsigned)
// Con sorgente di memoria
cmovz(rax, qword_ptr(rbx)); // 48 0F 44 03
cmovnz(eax, dword_ptr(rcx + 0x10)); // 0F 45 41 10
// Test bit
bt(eax, 5); // 0F BA E0 05
bt(rax, rbx); // 48 0F A3 D8
// Test bit e set/reset/complemento
bts(eax, 10); // 0F BA E8 0A (test e set)
btr(rax, rcx); // 48 0F B3 C8 (test e reset)
btc(edx, 3); // 0F BA FA 03 (test e complemento)
// Scansione bit
bsf(eax, ecx); // 0F BC C1 (scansiona in avanti per il primo 1)
bsr(rax, rbx); // 48 0F BD C3 (scansiona all'indietro per il primo 1)
// Conteggio popolazione e zeri iniziali/finali
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 (inverte ordine dei byte)
bswap(rax); // 48 0F C8
// Operazioni base su stringhe (operano su [rsi] e/o [rdi])
movsb(); // A4 (muovi byte [rsi] -> [rdi])
movsw(); // 66 A5
movsd(); // A5
movsq(); // 48 A5
cmpsb(); // A6 (confronta [rsi] con [rdi])
stosb(); // AA (memorizza al -> [rdi])
lodsb(); // AC (carica [rsi] -> al)
scasb(); // AE (confronta al con [rdi])
// Con prefisso REP (ripeti rcx volte)
rep_movsb(); // F3 A4 (memcpy)
rep_movsq(); // F3 48 A5 (memcpy veloce, 8 byte alla volta)
rep_stosb(); // F3 AA (memset)
rep_stosq(); // F3 48 AB
// Con REPE/REPNE (ripeti mentre uguale/diverso)
repe_cmpsb(); // F3 A6 (confronta stringhe fino a mismatch)
repne_scasb(); // F2 AE (cerca byte nella stringa)
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
// Chiamate di sistema
syscall_(); // 0F 05 (syscall a 64 bit)
sysenter(); // 0F 34
sysexit(); // 0F 35
// Interrupt
int3(); // CC (breakpoint)
int_(0x80); // CD 80 (syscall Linux a 32 bit)
int_(0x21); // CD 21 (interrupt DOS)
// Informazioni CPU
cpuid(); // 0F A2
rdtsc(); // 0F 31
rdtscp(); // 0F 01 F9
// Privilegio
cli(); // FA (clear interrupts)
sti(); // FB (set interrupts)
hlt(); // F4 (halt)
// Ritorno da interrupt
iret(); // CF (16 bit)
iretd(); // CF (32 bit)
iretq(); // 48 CF (64 bit)
Usa core::assemble() per concatenare array di byte di istruzioni:
constexpr auto prologue = core::assemble(
push(rbp),
mov(rbp, rsp),
sub(rsp, 0x20)
);
constexpr auto epilogue = core::assemble(
add(rsp, 0x20),
pop(rbp),
ret()
);
// Combinali
constexpr auto full_function = core::assemble(prologue, epilogue);
Aggiungi al tuo CMakeLists.txt:
include(FetchContent)
FetchContent_Declare(
static_asm
GIT_REPOSITORY https://github.com/mahmoudimus/static_asm.git
GIT_TAG v1.0.0 # o commit specifico
)
FetchContent_MakeAvailable(static_asm)
target_link_libraries(tuo_target PRIVATE static_asm::static_asm)
Clona o aggiungi come sottomodulo git:
git submodule add https://github.com/mahmoudimus/static_asm.git external/static_asm
Poi nel tuo CMakeLists.txt:
add_subdirectory(external/static_asm)
target_link_libraries(tuo_target PRIVATE static_asm::static_asm)
Quando incluso tramite add_subdirectory o FetchContent, viene aggiunto al progetto solo il target della libreria di interfaccia static_asm::static_asm. I test e gli esempi non vengono compilati a meno che non siano esplicitamente abilitati con -DSTATIC_ASM_BUILD_TESTS=ON.
Scarica static_asm.hpp dalla pagina delle release e includilo direttamente:
#include "static_asm.hpp"
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --install build --prefix /usr/local
Poi usa find_package:
find_package(static_asm REQUIRED)
target_link_libraries(tuo_target PRIVATE static_asm::static_asm)
# Configura
cmake -B build -DCMAKE_BUILD_TYPE=Release
# Compila
cmake --build build
# Esegui i test
ctest --test-dir build --output-on-failure
# Compila con esempi (solo Clang, utilizza assembly inline)
cmake -B build -DCMAKE_BUILD_TYPE=Release -DSTATIC_ASM_BUILD_EXAMPLES=ON
Supporto piattaforma:
Nota: La funzionalità core::emit() con assembly inline richiede Clang con ottimizzazione -O2.
Supporto operandi:
[base + index*scale + disp] con fattori di scala 1, 2, 4, 8Nota: Nessuna estensione SIMD/AVX ancora.
Questo progetto utilizza uv per gli strumenti Python (generazione di codice, amalgama singolo header).
# Installa uv (se non già installato)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Verifica installazione
uv --version
# Tutti gli script Python possono essere eseguiti direttamente con uv (le dipendenze sono gestite automaticamente)
uv run scripts/gen_from_x86ref.py --help
uv run scripts/amalgamate.sh
Strumenti richiesti:
Strumenti opzionali per lo sviluppo:
| Strumento | Scopo | Installazione |
|---|---|---|
| clang-format | Formattazione del codice | Tramite LLVM o pacchetto di sistema |
| clang-tidy | Analisi statica | Tramite LLVM o pacchetto di sistema |
Il progetto include un generatore che analizza il database XML x86reference:
# Mostra riepilogo del database istruzioni
uv run scripts/gen_from_x86ref.py
# Mostra dettagli per un'istruzione specifica
uv run scripts/gen_from_x86ref.py -i lea
uv run scripts/gen_from_x86ref.py -i imul
# Genera file del database istruzioni
uv run scripts/gen_from_x86ref.py --generate-db
# Genera file di test esaustivo
uv run scripts/gen_from_x86ref.py --generate-tests
instdb, prefix_db, prefix_0fdb)encoder.hpp o estendi un encoder esistenteNessuno strumento automatico può convertire in modo affidabile una libreria C++ multi-file arbitraria in una versione pulita solo header senza una preparazione manuale. Le seguenti tecniche aiutano a garantire che la tua libreria possa essere amalgamata con successo in un unico file header rimanendo corretta, manutenibile e conforme agli standard.
Nota: Questo progetto utilizza quom per l'amalgama e clang-tidy con il controllo
google-build-using-namespaceper automatizzare l'applicazione di queste regole. quom gestisce anche parzialmente la tecnica #4 (marcatori inline) attraverso le sue capacità di elaborazione.
using namespace nei file sorgenteusing namespace a livello di file nei file .cpp è pericoloso quando quei file vengono successivamente inclusi in un header — inquina lo spazio dei nomi globale per ogni unità di traduzione che include il tuo header.
Pattern consigliati invece:
// Preferito: avvolgi l'implementazione nello spazio dei nomi
namespace MyLib {
Foo::Foo() {
// ...
}
}
oppure
// Qualificazione esplicita (più verbosa ma molto chiara)
MyLib::Foo::Foo() {
// ...
}
Le API pubbliche dovrebbero risiedere nello spazio dei nomi principale. Tutto ciò che non è destinato agli utenti finali dovrebbe essere nascosto in uno spazio dei nomi annidato come detail o impl.
Convenzioni comuni:
namespace MyLib {
namespace detail { // molto usato
// classi interne, funzioni, ecc.
}
}
oppure
namespace MyLib::impl { // più corto, anch'esso comune
// dettagli interni di implementazione
}
C++17 e successivi supportano definizioni di spazi dei nomi annidati inline, che sono più pulite:
namespace MyLib::detail {
class InternalHelper { /* ... */ };
}
static inlineLe variabili static a livello di file definite nei file .cpp diventano problematiche in un mondo solo header (definizioni multiple, violazioni ODR).
Soluzione moderna (C++17+):
// Prima (in .cpp)
namespace MyLib {
static int s_counter = 0;
int next_id() {
return ++s_counter;
}
}
// Dopo (sicuro per header)
namespace MyLib::detail {
struct Globals {
static inline int counter = 0;
};
}
inline int MyLib::next_id() {
return ++detail::Globals::counter;
}
La variabile static inline garantisce di avere una singola definizione anche se inclusa più volte.
inlineQualsiasi funzione, funzione membro, costruttore o distruttore il cui corpo appare nell'header (ma non all'interno della definizione di classe) deve essere marcata inline per evitare violazioni della regola di definizione unica (ODR).
Poiché molti script di amalgama sono puramente testuali e non analizzano la semantica C++, una convenzione comune è usare una macro segnaposto (ad es. inline_t) durante lo sviluppo:
// MyLib.h (o header comune)
#define inline_t /* vuoto durante le build normali */
// MyLib.cpp (durante lo sviluppo)
namespace MyLib::detail {
inline_t void Helper::do_work() {
// implementazione
}
}
Durante l'amalgama, lo strumento sostituisce inline_t con inline:
// Dopo amalgama / trasformazione
inline void MyLib::detail::Helper::do_work() {
// ...
}
Puoi scegliere qualsiasi nome di macro preferisci (ad es. MYLIB_INLINE, INLINE_IMP, ecc.) e configurare di conseguenza il tuo script di amalgama.
Nota: Strumenti come quom possono automatizzare parzialmente questo processo comprendendo le semantiche di inclusione C++ e gestendo correttamente le definizioni di funzione durante l'amalgama, riducendo la necessità di marcatori
inlinemanuali in molti casi.
using namespace … a livello di namespace/file nei file di implementazione.detail / impl).static a livello di file con membri static inline di una struct/classe.inline (sostituita durante l'amalgama).Seguire queste quattro pratiche rende la transizione a una distribuzione singolo header molto più fluida e molto meno soggetta a errori — anche quando si utilizzano strumenti di amalgama puramente testuali.
Questo progetto è basato su cx_assembler di Midi12. La libreria originale ha fornito le basi per la codifica di assembly x86 in fase di compilazione in C++.
| Categoria | Istruzioni |
|---|
| ALU | ADD, ADC, SUB, SBB, AND, OR, XOR, CMP, TEST |
| Unarie | INC, DEC, NEG, NOT |
| Moltiplicazione/Divisione | MUL, IMUL (forme 1/2/3 operandi), DIV, IDIV |
| Movimento dati | MOV, MOVABS, MOVZX, MOVSX, MOVSXD, LEA, XCHG, PUSH, POP |
| Shift/Rotazione | SHL, SHR, SAL, SAR, ROL, ROR, RCL, RCR |
| Flusso di controllo | JMP, CALL, RET, RETF |
| Salti condizionati | JZ/JE, JNZ/JNE, JB/JC, JNB/JNC, JBE/JNA, JNBE/JA, JL, JNL, JLE, JNLE, JO, JNO, JS, JNS, JP, JNP (offset a 8 e 32 bit) |
| Mosse condizionate | CMOVA, CMOVAE, CMOVB, CMOVBE, CMOVE, CMOVG, CMOVGE, CMOVL, CMOVLE, CMOVNE, CMOVNO, CMOVNP, CMOVNS, CMOVO, CMOVP, CMOVS |
| Operazioni bit | BT, BTC, BTR, BTS |
| Scansione/conteggio bit | BSF, BSR, POPCNT, LZCNT, TZCNT, BSWAP |
| Operazioni stringhe | MOVSB/W/D/Q, CMPSB/W/D/Q, LODSB/W/D/Q, STOSB/W/D/Q, SCASB/W/D/Q (con prefissi REP/REPE/REPNE) |
| Sistema | SYSCALL, SYSENTER, SYSEXIT, INT, INT3, IRET/D/Q, CLI, STI, HLT, CPUID, RDTSC, RDTSCP |
| Varie | NOP, UD2 |
| Strumento | Scopo | Installazione |
|---|
| uv | Gestore pacchetti/progetti Python | curl -LsSf https://astral.sh/uv/install.sh | sh |
| quom | Amalgama singolo header | uv tool install quom |
| CMake 3.19+ | Sistema di build | cmake.org |
| Compilatore C++20 | GCC 11+, Clang 14+, MSVC 2022+ | - |