Skip to content

Protect Function macro

The PROTECT_FUNCTION macro virtualizes the complete native function that contains it. Unlike a normal Virtualizer macro, it does not require separate START and END markers.

Place the directive anywhere inside the function that you want to protect:

#include "VirtualizerSDK.h"

int ValidateLicense(int value)
{
    VIRTUALIZER_PROTECT_FUNCTION();

    // The complete ValidateLicense function is virtualized.
    return value == 1234;
}

VIRTUALIZER_PROTECT_FUNCTION() uses the Virtual Machine assigned to generic Virtualizer macros. To select a specific Virtual Machine, include its name in the directive:

int ValidateLicense(int value)
{
    VIRTUALIZER_TIGER_WHITE_PROTECT_FUNCTION();
    return value == 1234;
}

The shorter VM_PROTECT_FUNCTION() and VM_TIGER_WHITE_PROTECT_FUNCTION() names are equivalent aliases. Every available Virtual Machine family and variant provides a corresponding VIRTUALIZER_<VM>_PROTECT_FUNCTION() and VM_<VM>_PROTECT_FUNCTION() directive.

Symbol file requirement

Code Virtualizer must obtain the exact start and end addresses of the containing function. The required symbol source depends on the executable format:

  • PE (Windows): A matching sibling .pdb or .map file is required.

  • ELF: Code Virtualizer first uses native function symbols from .symtab or .dynsym. A sibling .map file can provide the function range when suitable native symbols are not available.

  • Mach-O: A matching sibling .map file is required.

The symbol source must contain the function that includes the directive. If Code Virtualizer cannot find an unambiguous containing function, it reports the macro as unresolved and does not guess the function boundaries.

The macro does not take a name or label. Code Virtualizer obtains the name displayed in the Protection Macros panel from the resolved function symbol.

Calls between protected functions

Functions protected independently with PROTECT_FUNCTION remain separate virtualized regions. Code Virtualizer does not merge them into one continuous VM-to-VM execution flow.

For example:

int FunctionB(int value)
{
    VIRTUALIZER_FISH_BLACK_PROTECT_FUNCTION();
    return value * 2;
}

int FunctionA(int value)
{
    VIRTUALIZER_FISH_BLACK_PROTECT_FUNCTION();
    return FunctionB(value) + 1;
}

The call instruction inside FunctionA is virtualized. Because FunctionB is an out-of-line function, execution still passes through its callable entry point. That entry redirects execution into the independently virtualized code for FunctionB. When FunctionB returns, execution resumes in the virtualized flow of FunctionA.

The same Virtual Machine type may be reused internally for both functions, but this is another virtualized entry or invocation, not a direct continuation from one VM region into the other. The callable entry point of FunctionB remains an intermediate boundary that could be targeted or intercepted.

If eliminating that intermediate entry boundary is a strict requirement, keep the helper function inline. If separate callable entries are acceptable, leave the functions separate and protect each function independently.

Remarks

  • Keep the target code as a separate native function. Prevent compiler or link-time inlining when necessary so the directive remains inside the intended function.

  • The directive protects the complete resolved function, including instructions located before the directive call.

  • Use only one PROTECT_FUNCTION directive in a function.