You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MSVC x64环境下如何获取__movsq指令地址并将其NOP化?

动态替换x64汇编指令(MSVC++ 2022)

我基于MSVC++ VS 2022的x64 C++模式编写了DispatchOptimizationLoop函数,其中通过编译器内联函数__movsq将全局缓冲区g_table的数据复制到g_localimport。需求是:当全局标志g_Reduce被设置后,后续调用该函数时,把__movsq对应的指令替换为nop。

我尝试的示例代码如下:

// compiler: MSVC++ VS 2022 in C++ mode; x64
void DispatchOptimizationLoop()
{
    __movsq(g_table, g_localimport, 23);

    // 希望在movsq后生成nop?
    static unsigned char* ptr = (unsigned char*)(&__nop);

    if (!InterlockedExchange8(g_Reduce, 1))
    {
        // 指向内存中的movsq指令
        ptr -= 3;

        // 替换为nop
        ...
    }

    // 函数剩余逻辑
    ...
}

我的思路是在__movsq后添加nop,通过nop的地址回推__movsq的起始地址,再用3个0x90覆盖该指令。但我知道代码中(unsigned char*)(&__nop)并不会实际生成nop,只是示意想法。请问这种方案是否可行?或者有没有更好的方法存储需要被NOP化的指令地址?


原方案不可行的原因

你的思路存在两个核心问题:

  1. &__nop不会触发编译器生成实际的nop指令,编译器会直接忽略这个空操作的地址引用,甚至在优化模式下完全丢弃相关代码,导致无法获取有效地址。
  2. 即使手动调用__nop()生成空指令,编译器的优化策略、代码布局(比如插入的对齐指令)会导致nop与__movsq的偏移量不确定,固定减3的操作在不同编译选项(如O0/O2)下完全不可靠。

更可靠的实现方案

直接通过汇编标签标记__movsq的起始地址,这是获取指令位置最稳定的方式,不受编译优化影响。同时要注意修改代码段内存属性(默认只读),以及原子操作避免多线程竞态。

示例代码如下:

// compiler: MSVC++ VS 2022 in C++ mode; x64
#include <intrin.h>
#include <windows.h>

// 全局标志和缓冲区定义
volatile char g_Reduce = 0;
void* g_table;
void* g_localimport;

void DispatchOptimizationLoop()
{
    // 用__label__定义本地标签,标记__movsq的起始位置
    __label__ movsq_entry;
    __asm movsq_entry:
    __movsq(g_table, g_localimport, 23);

    static unsigned char* movsq_addr = reinterpret_cast<unsigned char*>(&&movsq_entry);
    static bool is_patched = false;

    // 原子检查并设置g_Reduce,确保只有一个线程执行补丁
    if (!is_patched && !InterlockedExchange8(&g_Reduce, 1))
    {
        DWORD old_protect;
        // 修改内存属性为可写可执行(代码段默认只读)
        VirtualProtect(movsq_addr, 3, PAGE_EXECUTE_READWRITE, &old_protect);

        // x64下__movsq生成的是rep movsq指令,字节码为F3 48 A5,共3字节
        // 用3个nop(0x90)覆盖
        movsq_addr[0] = 0x90;
        movsq_addr[1] = 0x90;
        movsq_addr[2] = 0x90;

        // 恢复原内存属性
        VirtualProtect(movsq_addr, 3, old_protect, &old_protect);
        is_patched = true;
    }

    // 函数剩余逻辑
    // ...
}

关键注意事项

  • 内存属性修改:代码段默认是PAGE_EXECUTE_READ权限,直接写入会触发访问违规,必须用VirtualProtect临时改为PAGE_EXECUTE_READWRITE,修改后恢复原权限。
  • 原子性与幂等性:用InterlockedExchange8原子设置g_Reduce,配合is_patched静态变量,确保补丁只执行一次,避免多线程下重复修改导致的未定义行为。
  • 指令长度确认:x64平台下__movsq(count)生成的是rep movsq指令,固定为3字节(F3 48 A5),所以用3个nop覆盖刚好匹配长度,不会破坏后续指令的对齐。

内容的提问来源于stack exchange,提问作者Arush Agarampur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 23:25:33