Add operand-normalized semantic code signatures

New semsig_hash(mem_view_t) primitive (include/semsig.h): a position-,
register-, immediate- and instruction-order-invariant function fingerprint.
It folds per-instruction operand-canonical tokens, splitting the body into
basic blocks via cfg_blocks and using a within-block order-insensitive
sort-then-fold (between-block order preserved), so it survives compiler
register reallocation and instruction scheduling that the byte-mask
func_hash/sig_generate cannot. Distinct hash domain from func_hash.

The operand decode is delegated to an OPTIONAL external disassembler behind
a private adapter seam (src/handlers/semsig_backend.h); semsig.c stays
backend-neutral and includes no backend header. VMIE_DISASM={OFF|zydis|
capstone} gates the build: OFF (default) compiles only semsig_stub.c and
keeps the zero-dependency build (VMIE_HAVE_DISASM=0, packages unchanged).
The backend is brought from VMIE_DISASM_SRC or find_package - never
vendored. Adds the vmie_win32_func_semsig wrapper.

The CI deb job builds with the Zydis backend (libzydis-dev); the runtime
package dependency on libzydis is derived automatically by dpkg-shlibdeps.
This commit is contained in:
2026-06-28 21:51:47 +03:00
parent 8ca84a5861
commit 1b06206043
10 changed files with 955 additions and 1 deletions
+73
View File
@@ -12,6 +12,18 @@ include(GNUInstallDirs)
option(VMIE_LTO "Enable LTO" OFF) # build-only; shipped default is -O2, no LTO
# ---- optional semantic-signature backend (operand disassembler) ----------
# VMIE_DISASM selects the OPTIONAL operand-decode backend for the semantic
# signature feature (semsig.h). OFF (default) ships the 0.1.6 behaviour: the
# generic symbol exists via semsig_stub.c, returns 0, VMIE_HAVE_DISASM=0, and no
# external library is pulled - CI and the .deb packages are unchanged. A backend
# (zydis|capstone) is NEVER vendored: it is brought from OUTSIDE the tree, either
# by VMIE_DISASM_SRC (a path to the backend's own source tree, add_subdirectory'd)
# or, failing that, by find_package of an already-installed library.
set(VMIE_DISASM "OFF" CACHE STRING "Semantic-signature backend: OFF | zydis | capstone")
set_property(CACHE VMIE_DISASM PROPERTY STRINGS OFF zydis capstone)
set(VMIE_DISASM_SRC "" CACHE PATH "Path to the disassembler backend source tree (external; add_subdirectory'd)")
# ---- host: VMI core objects (single owner of the source list) -----------
add_library(vmie_obj OBJECT
src/core/gpa.c
@@ -31,6 +43,23 @@ add_library(vmie_obj OBJECT
src/handlers/x86dec.c
src/handlers/pmap.c
src/handlers/snapdiff.c)
# semantic-signature TUs: OFF compiles ONLY the stub (which carries the whole
# generic symbol and returns 0); a backend compiles the core (semsig.c) + the one
# backend TU that talks to the disassembler. semsig.c is never built in OFF (it
# calls the backend decode, absent there) - so there is no dead/unresolved call.
if(VMIE_DISASM STREQUAL "OFF")
target_sources(vmie_obj PRIVATE src/handlers/semsig_stub.c)
elseif(VMIE_DISASM STREQUAL "zydis")
target_sources(vmie_obj PRIVATE src/handlers/semsig.c
src/handlers/semsig_backend_zydis.c)
elseif(VMIE_DISASM STREQUAL "capstone")
target_sources(vmie_obj PRIVATE src/handlers/semsig.c
src/handlers/semsig_backend_capstone.c)
else()
message(FATAL_ERROR "VMIE_DISASM must be OFF, zydis, or capstone (got '${VMIE_DISASM}')")
endif()
target_include_directories(vmie_obj
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include # public API: include/*.h
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/core/include # private: core.h
@@ -42,11 +71,49 @@ if(VMIE_LTO)
target_link_options(vmie_obj PRIVATE -flto)
endif()
# Resolve the backend target/package name (the actual link/define is applied to
# vmie_obj here AND re-applied to the static/shared libs below, because
# $<TARGET_OBJECTS:vmie_obj> carries object files only - not link/define usage
# requirements). The backend is PRIVATE (an implementation detail), while
# VMIE_HAVE_DISASM is PUBLIC (visible through semsig.h). The backend comes from
# VMIE_DISASM_SRC (external sources) or find_package - never from in-tree sources.
if(NOT VMIE_DISASM STREQUAL "OFF")
if(VMIE_DISASM STREQUAL "zydis")
set(VMIE_DISASM_LINK "Zydis::Zydis")
set(_vmie_disasm_pkg "Zydis")
else()
set(VMIE_DISASM_LINK "capstone::capstone")
set(_vmie_disasm_pkg "capstone")
endif()
if(VMIE_DISASM_SRC)
# Build the backend from its OWN external source tree (kept out of this
# repo). Its CMake exports the imported target linked below.
add_subdirectory(${VMIE_DISASM_SRC} ${CMAKE_CURRENT_BINARY_DIR}/_disasm_backend)
else()
find_package(${_vmie_disasm_pkg} REQUIRED)
endif()
target_link_libraries(vmie_obj PRIVATE ${VMIE_DISASM_LINK})
target_compile_definitions(vmie_obj PUBLIC VMIE_HAVE_DISASM=1)
else()
set(VMIE_DISASM_LINK "")
target_compile_definitions(vmie_obj PUBLIC VMIE_HAVE_DISASM=0)
endif()
# ---- host: static library (in-tree consumers: CLIs + test) --------------
# $<TARGET_OBJECTS:> carries object files only, not usage requirements:
# re-declare the PUBLIC include scope so vmie_cli/vmie_scan still see include/.
add_library(vmie STATIC $<TARGET_OBJECTS:vmie_obj>)
target_include_directories(vmie PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
# Re-apply the feature macro (PUBLIC: seen by in-tree consumers + the test) and
# the backend link (PRIVATE), which $<TARGET_OBJECTS> does not carry over.
if(VMIE_DISASM STREQUAL "OFF")
target_compile_definitions(vmie PUBLIC VMIE_HAVE_DISASM=0)
else()
target_compile_definitions(vmie PUBLIC VMIE_HAVE_DISASM=1)
target_link_libraries(vmie PRIVATE ${VMIE_DISASM_LINK})
endif()
# ---- host: shared library (external delivery: libvmie.so.*) -------------
add_library(vmie_shared SHARED $<TARGET_OBJECTS:vmie_obj>)
@@ -58,6 +125,12 @@ set_target_properties(vmie_shared PROPERTIES
if(VMIE_LTO)
target_link_options(vmie_shared PRIVATE -flto)
endif()
if(VMIE_DISASM STREQUAL "OFF")
target_compile_definitions(vmie_shared PUBLIC VMIE_HAVE_DISASM=0)
else()
target_compile_definitions(vmie_shared PUBLIC VMIE_HAVE_DISASM=1)
target_link_libraries(vmie_shared PRIVATE ${VMIE_DISASM_LINK})
endif()
# ---- host: CLI demonstrator over the library ----------------------------
add_executable(vmie_cli src/cli.c)