mirror of
https://dev.lirent.ru/Vatrog/vm-introspection-engine.git
synced 2026-07-08 23:46:36 +03:00
107 lines
5.7 KiB
C
107 lines
5.7 KiB
C
|
|
/* codetrace.h - generic (OS-agnostic) register def-use over one function.
|
||
|
|
*
|
||
|
|
* Handler layer, gated like semsig.h: a process-scoped, STATIC register def-use
|
||
|
|
* analysis built on insn_decode (insndec.h) and the basic-block partition of
|
||
|
|
* cfg_blocks (codeanalysis.h, REUSED - this header writes no second splitter).
|
||
|
|
* For one function view it computes per-instruction register read (use) / write
|
||
|
|
* (def) sets - the building material a caller needs for reaching-definitions.
|
||
|
|
*
|
||
|
|
* It returns DATA. It does NOT execute or model values, does NOT unroll loops,
|
||
|
|
* does NOT build use-def CHAINS, and carries no dataflow solver: those are the
|
||
|
|
* caller's analysis. v1 deliberately stops at per-instruction use/def so the
|
||
|
|
* surface does not predetermine the caller's algorithm.
|
||
|
|
*
|
||
|
|
* Family placement. codeanalysis.h is PURE / always available; semsig.h is gated
|
||
|
|
* on the disassembler backend. codetrace.h sits in the second row with semsig: it
|
||
|
|
* needs the backend decode (via insndec.h) plus cfg_blocks. With no backend the
|
||
|
|
* symbol still exists (ABI stable) but returns the sentinel, and VMIE_HAVE_DISASM
|
||
|
|
* is 0. The chosen split keeps codeanalysis.h's "zero-dep, works always" contract
|
||
|
|
* literally true.
|
||
|
|
*
|
||
|
|
* GENERIC / OS-agnostic. The analysis names no OS. A calling convention enters
|
||
|
|
* ONLY through the explicit trace_abi argument, which selects a static caller-
|
||
|
|
* saved (volatile) clobber table applied at call sites. Win64 is just one enum
|
||
|
|
* value, not a hardcoded dependency; TRACE_ABI_NONE reproduces conservative
|
||
|
|
* behavior for any non-Windows consumer. The OS-specific CHOICE of Win64 is made
|
||
|
|
* by the win32 wrapper (vmie_win32_func_usedef, win32.h), not by this header.
|
||
|
|
*/
|
||
|
|
#ifndef VMIE_CODETRACE_H
|
||
|
|
#define VMIE_CODETRACE_H
|
||
|
|
#include <stdint.h>
|
||
|
|
#include <stddef.h>
|
||
|
|
#include "memmodel.h" /* mem_view_t (the single owner of the view type) */
|
||
|
|
|
||
|
|
/* Shared feature gate (see semsig.h / insndec.h). Default keeps the header valid
|
||
|
|
* when compiled outside the project build. */
|
||
|
|
#ifndef VMIE_HAVE_DISASM
|
||
|
|
#define VMIE_HAVE_DISASM 0
|
||
|
|
#endif
|
||
|
|
|
||
|
|
/* Calling convention applied to call sites when computing def-use. The analysis
|
||
|
|
* itself stays OS-agnostic: an ABI is just a parameter selecting a static
|
||
|
|
* caller-saved (volatile) clobber table. An unknown value is treated as
|
||
|
|
* TRACE_ABI_NONE (conservative), not an error. */
|
||
|
|
typedef enum {
|
||
|
|
TRACE_ABI_NONE = 0, /* conservative: a call clobbers only what insn_decode
|
||
|
|
* reports architecturally (rsp/rip write, indirect
|
||
|
|
* target read); no volatile registers are killed. */
|
||
|
|
TRACE_ABI_WIN64 /* standard Win64: volatile GPR/vec/FLAGS are killed
|
||
|
|
* (full-kill) across every call site (see codetrace.c
|
||
|
|
* for the exact set and its honest limits). */
|
||
|
|
} trace_abi;
|
||
|
|
|
||
|
|
/* A compact register set over canonical reg_id roots (a bitset). The field split
|
||
|
|
* is an implementation detail of the struct; consumers should treat it opaquely
|
||
|
|
* and compare/inspect via the documented def/use semantics, not the raw words.
|
||
|
|
* gpr - the 16 GPR roots (REGID_RAX..REGID_R15)
|
||
|
|
* vec_lo - vector roots REGID_VEC0..REGID_VEC31
|
||
|
|
* misc - mask roots (k0..k7), REGID_FLAGS, REGID_OTHER */
|
||
|
|
typedef struct {
|
||
|
|
uint16_t gpr; /* bit i set => REGID_RAX + i present (16 GPR roots) */
|
||
|
|
uint32_t vec_lo; /* bit i set => REGID_VEC0 + i present (32 vector roots) */
|
||
|
|
uint16_t misc; /* bit i set => (REGID_K0 + i) for i<8, then FLAGS, OTHER */
|
||
|
|
} regset;
|
||
|
|
|
||
|
|
/* Per-instruction use/def in the function view's coordinate space.
|
||
|
|
* off - byte offset of the instruction within fn
|
||
|
|
* len - instruction length
|
||
|
|
* use - roots READ (incl. implicit, incl. address regs of mem operands)
|
||
|
|
* def - roots WRITTEN as a FULL/zero-extending write (these KILL the
|
||
|
|
* enclosing root); for a call site under a non-NONE trace_abi the
|
||
|
|
* ABI volatile clobber is merged here as a full kill too
|
||
|
|
* def_partial - roots written PARTIALLY (8/16-bit GPR, legacy-SSE): they do NOT
|
||
|
|
* kill the enclosing root, so a wider live root survives
|
||
|
|
* cond_def - roots written CONDITIONALLY (cmovcc/setcc): do not kill either */
|
||
|
|
typedef struct {
|
||
|
|
uint32_t off;
|
||
|
|
uint8_t len;
|
||
|
|
regset use, def, def_partial, cond_def;
|
||
|
|
} insn_usedef;
|
||
|
|
|
||
|
|
/* Compute per-instruction use/def for one function view under calling convention
|
||
|
|
* `abi`. `fn` is a view spanning EXACTLY one function (as for cfg_blocks /
|
||
|
|
* semsig_hash). REUSES cfg_blocks only to validate the partition and iterate in
|
||
|
|
* block order; the use/def itself comes from insn_decode per instruction.
|
||
|
|
*
|
||
|
|
* `abi` selects the call-site volatile clobber: TRACE_ABI_NONE = conservative (no
|
||
|
|
* volatile kill, only the architectural rsp/rip write insn_decode reports);
|
||
|
|
* TRACE_ABI_WIN64 = full-kill of the Win64 volatile roots at every call site.
|
||
|
|
* The analysis stays OS-agnostic: `abi` is just a parameter, not a hardcoded OS.
|
||
|
|
*
|
||
|
|
* Writes up to `max` records in ascending off order (out=NULL to count). Returns
|
||
|
|
* the TOTAL instruction count, 0 if `fn` is empty, or -1 on a decode desync
|
||
|
|
* (cfg_blocks could not split, or the backend could not decode an instruction) /
|
||
|
|
* a build with no backend (VMIE_HAVE_DISASM==0). The sentinel convention mirrors
|
||
|
|
* cfg_blocks: -1 = desync/no-backend, 0 = empty.
|
||
|
|
*
|
||
|
|
* PURE: only the view and the decoder, no vmie_mem / no I/O (a stack buffer with
|
||
|
|
* a heap spill only when the block count overflows, like semsig_hash).
|
||
|
|
*
|
||
|
|
* Example - registers killed by each instruction of a function:
|
||
|
|
* insn_usedef ud[512];
|
||
|
|
* int n = func_usedef(fn, TRACE_ABI_WIN64, ud, 512);
|
||
|
|
* for (int i = 0; i < n && i < 512; i++) ... inspect ud[i].def ... */
|
||
|
|
int func_usedef(mem_view_t fn, trace_abi abi, insn_usedef* out, int max);
|
||
|
|
|
||
|
|
#endif /* VMIE_CODETRACE_H */
|