NDArray Tutorial - mxnet

本文详细介绍了MXNet中NDArray的基础操作,包括创建、数据类型指定、填充、基本运算、切片、形状调整、降维、广播及复制等,并演示了如何在GPU上进行计算。

根据 mxnet documentation-NDArray
NDArray 是 mxnet 用于计算的数据结构. 可类比于 tensorflow 的 tensor,caffe 的 blob.
看到 mxnet 中的 NDArray 使用与 numpy 差别不大, 操作都很简明.
这个网页都是基础操作, 我记录了其中较关键的部分.

创建 NDArray

NDArray 既可直接创建, 也可以从 numpy 的 ndarray 转换得到.

a = mx.nd.array([1,2,3])

c = np.arange(15).reshape(3,5)
# create a 2-dimensional array from a numpy.ndarray object
a = mx.nd.array(c)

制定数据类型

默认是 float32, 如需改变可通过 dtype 设置.

# float32 is used in default
a = mx.nd.array([1,2,3])
# create an int32 array
b = mx.nd.array([1,2,3], dtype=np.int32)
# create a 16-bit float array
c = mx.nd.array([1.2, 2.3], dtype=np.float16)

填充函数

# create a 2-dimensional array full of zeros with shape (2,3)
a = mx.nd.zeros((2,3))
# create a same shape array full of ones
b = mx.nd.ones((2,3))
# 全是7
c = mx.nd.full((2,3), 7)
# 随机值
d = mx.nd.empty((2,3))

打印

使用asnumpy来打印 NDArray

b = mx.nd.arange(18).reshape((3,2,3))
b.asnumpy()

基本操作

与 numpy 一样, * 代表矩阵对应元素相乘 (点乘),dot表示矩阵相乘.

a = mx.nd.arange(4).reshape((2,2))
b = a * a
c = mx.nd.dot(a,a)

切片

切片操作默认在 axis=0, 即上. 也可通过slice_axis来确定在哪个维度切片.

#按行切, 设置序号为1的行内数据为1
a = mx.nd.array(np.arange(6).reshape(3,2))
a[1:2] = 1
a[:].asnumpy()
#按列切, 取出序号为1的列(从0开始
d = mx.nd.slice_axis(a, axis=1, begin=1, end=2)
d.asnumpy()

形状

除了 axis=1 上可以不同, 被concat的矩阵其他维度都要相同.
如 a.shape:(1L,2L,3L,4L),b.shape:(1L,10L,3L,4L)
那么 c = mx.nd.concat(a,b)
c.shape:(1L, 12L, 3L, 4L)

降维

使用summean, 从特定的维度sum_axis

a = mx.nd.ones((2,3))
b = mx.nd.sum(a)
c = mx.nd.sum_axis(a, axis=1)

广播

使用broadcast_to方法.
在使用 + 或 *, 两个矩阵维度不同的时候, 会自动广播.

a = mx.nd.array(np.arange(6).reshape(6,1))
b = a.broadcast_to((6,4))
#多维度广播
c = a.reshape((2,1,1,3))
d = c.broadcast_to((2,2,2,3))
#自动广播
a = mx.nd.ones((3,2))
b = mx.nd.ones((1,2))
c = a + b

复制

a = mx.nd.ones((2,2))
b = a
b is a # will be True

b = a.copy()
b is a  # will be False

GPU 支持

NDArray 设备信息存放在 ndarray.context 中.

gpu_device=mx.gpu() # Change this to mx.cpu() in absence of GPUs.

def f():
    a = mx.nd.ones((100,100))
    b = mx.nd.ones((100,100))
    c = a + b
    print(c)
# in default mx.cpu() is used
f()
# change the default context to the first GPU
with mx.Context(gpu_device):
    f()

mxnet 计算的所需的数据结构需存在相同的设备中. 可使用copyto方法, 将 cpu 数据转存到 gpu 中.

文件读写

推荐使用saveload方法. 因为其支持多语言, 兼容分布式.

d = {'a':a, 'b':b}
mx.nd.save("temp.ndarray", d)
c = mx.nd.load("temp.ndarray")
alloc(newmem,2048,"Tutorial-x86_64.exe"+4997F) label(returnhere) label(originalcode) label(exit) newmem: //this is allocated memory, you have read,write,execute access //place your code here originalcode: add [rbp+48],dl mov ebp,esp lea rsp,[rsp-60] mov [rbp-40],rbx mov [rbp-38],rsi mov rbx,rcx mov esi,edx mov qword ptr [rbp-20],00000000 mov qword ptr [rbp-08],00000000 nop movss xmm0,[rbx+08] comiss xmm0,["Tutorial-x86_64.exe"+26CD38] jp "Tutorial-x86_64.exe"+499C8 jne "Tutorial-x86_64.exe"+499C8 mov rcx,["Tutorial-x86_64.exe"+1DE578] call "Tutorial-x86_64.exe"+15B410 jmp "Tutorial-x86_64.exe"+49A5E cvtsi2ss xmm0,esi movss xmm1,[rbx+08] subss xmm1,xmm0 comiss xmm1,["Tutorial-x86_64.exe"+26CD38] jp "Tutorial-x86_64.exe"+499EA jae "Tutorial-x86_64.exe"+499EA movss xmm0,["Tutorial-x86_64.exe"+26CD38] jmp "Tutorial-x86_64.exe"+499ED movaps xmm0,xmm1 movss [rbx+08],xmm0 comiss xmm0,["Tutorial-x86_64.exe"+26CD38] jp "Tutorial-x86_64.exe"+49A0F jne "Tutorial-x86_64.exe"+49A0F mov rcx,[rbx+60] mov rdx,["Tutorial-x86_64.exe"+1DE598] call "Tutorial-x86_64.exe"+B0420 jmp "Tutorial-x86_64.exe"+49A51 movss xmm1,[rbx+08] lea rcx,[rbp-20] call "Tutorial-x86_64.exe"+674B0 mov rax,[rbp-20] mov [rbp-10],rax mov qword ptr [rbp-18],0000000B lea r8,[rbp-18] mov rdx,["Tutorial-x86_64.exe"+1DE5B8] xor r9,r9 lea rcx,[rbp-08] call "Tutorial-x86_64.exe"+65790 mov rdx,[rbp-08] mov rcx,[rbx+60] call "Tutorial-x86_64.exe"+B0420 mov ecx,004C4B40 call "Tutorial-x86_64.exe"+104A0 mov [rbx+10],eax nop exit: jmp returnhere "Tutorial-x86_64.exe"+4997F: jmp newmem nop DB returnhere: 逐个代码意义分析
最新发布
12-01
你提供的这段代码是 **Cheat Engine 中典型的内存注入脚本(code injection)**,用于在 64 位程序 `Tutorial-x86_64.exe` 的特定地址处插入自定义逻辑。我们将 **逐行详细分析每条指令的作用、目的和上下文意义**。 --- ## 🔍 整体结构概览 ```asm alloc(newmem,2048,...) ; 分配新内存块 label(...) ; 定义跳转标签 newmem: ; 注入代码区域 // 可插入自定义逻辑 originalcode: ; 复制原始指令开始 ... ; 原始汇编指令 exit: jmp returnhere ; 跳回原程序流程 "Tutorial-x86_64.exe"+4997F: ; Hook 点 jmp newmem ; 跳转到我们的代码 nop DB ; 补齐字节(填充) returnhere: ; 返回点(执行后续原指令) ``` 这是标准的“钩子 + 恢复”模式,防止修改破坏原始行为。 --- ## ✅ 逐行代码详解 ### 1. 内存分配与标签定义 ```asm alloc(newmem,2048,"Tutorial-x86_64.exe"+4997F) ``` - 在靠近 `"Tutorial-x86_64.exe"+4997F` 地址处分配 2048 字节可读写执行内存。 - `newmem` 是这块内存的起始标签,用于存放我们复制的原始代码和自定义逻辑。 - 目的是确保 `jmp newmem` 是短跳转(相对偏移有效)。 ```asm label(returnhere) label(originalcode) label(exit) ``` - 声明三个符号标签,供后续跳转使用: - `returnhere`: 执行完注入代码后返回的位置。 - `originalcode`: 新内存中原始指令的起点。 - `exit`: 统一出口,跳回原流程。 --- ### 2. 新内存区:`newmem` ```asm newmem: //place your code here ``` - 这是你 **可以添加自定义代码的地方**,比如打印寄存器值、修改参数、拦截函数调用等。 - 当前为空,表示不做额外操作。 > ⚠️ 如果你要加代码,请记得保护使用的寄存器(如 push/pop),避免影响原程序状态。 --- ### 3. 开始复制原始指令(从原地址 `"Tutorial-x86_64.exe"+4997F` 搬来的) #### ➤ `add [rbp+48], dl` ```asm add [rbp+48], dl ``` - 将 8 位寄存器 `dl`(`edx` 的低 8 位)加到 `[rbp+48]` 指向的内存字节上。 - 很可能是某种计数器或标志位更新。 #### ➤ `mov ebp, esp` ```asm mov ebp, esp ``` - 设置栈帧基址:将当前栈顶 `esp` 赋给 `ebp`。 - 典型函数序言的一部分(虽然这里不是开头),可能用于建立局部变量访问基础。 #### ➤ `lea rsp, [rsp-60]` ```asm lea rsp, [rsp-60] ``` - 手动调整栈指针,向下移动 0x60 (96) 字节,为局部变量腾出空间。 - 不同于 `sub rsp, 60`,`lea` 更高效,且不影响标志位。 #### ➤ `mov [rbp-40], rbx` ```asm mov [rbp-40], rbx ``` - 保存寄存器 `rbx` 到栈上的局部变量位置 `[rbp-40]`。 - 遵循调用约定:被调用者需保存某些寄存器。 #### ➤ `mov [rbp-38], rsi` ```asm mov [rbp-38], rsi ``` - 同样保存 `rsi` 寄存器。 #### ➤ `mov rbx, rcx` ```asm mov rbx, rcx ``` - 把第一个参数(`rcx`,x64 调用约定中第一个整型参数)传给 `rbx`。 - 表明此函数很可能处理一个对象,`rbx` 成为该对象的主指针。 #### ➤ `mov esi, edx` ```asm mov esi, edx ``` - 把第二个参数 `edx` 存入 `esi`(准备作为后续函数参数)。 - 注意:`edx` 是 32 位,高位会被清零(x64 规则)。 #### ➤ 初始化局部变量 ```asm mov qword ptr [rbp-20], 00000000 mov qword ptr [rbp-08], 00000000 ``` - 清空两个 64 位局部变量(可能是临时缓冲区或状态存储)。 #### ➤ `nop` ```asm nop ``` - 空操作,占一个字节,常用于对齐或填充。 - 可能原指令流中有对齐需求。 --- ### 4. 浮点比较逻辑:检查 `[rbx+08] == 0 ?` ```asm movss xmm0, [rbx+08] ``` - 将对象偏移 `+08` 处的单精度浮点数加载到 `xmm0`。 - 极有可能是某个属性,如坐标 X/Y/Z、速度、血量等。 ```asm comiss xmm0, ["Tutorial-x86_64.exe"+26CD38] ``` - 比较 `xmm0` 和全局地址 `26CD38` 处的 float 值(很可能是 `0.0f` 或 epsilon)。 - `COMISS` 会设置 ZF/CF/PF 标志。 ```asm jp "Tutorial-x86_64.exe"+499C8 jne "Tutorial-x86_64.exe"+499C8 ``` - `jp`:Jump if Parity → 如果结果有奇偶性错误(PF=1),通常意味着其中一个操作数是 **NaN**。 - `jne`:不相等则跳转。 - 👉 合起来意思是: > 如果 `[rbx+08] != 0.0` 或它是 NaN,则跳走 → 即只有当它精确等于 0 时才继续。 👉 **用途推测**:判断某个值是否“无效”或“未初始化”。 --- ### 5. 调用初始化函数(若值为 0) ```asm mov rcx, ["Tutorial-x86_64.exe"+1DE578] call "Tutorial-x86_64.exe"+15B410 ``` - 将全局指针 `1DE578` 作为 `rcx` 参数传递。 - 调用函数 `15B410` —— 很可能是某种资源创建或初始化函数。 ```asm jmp "Tutorial-x86_64.exe"+49A5E ``` - 跳过中间所有逻辑,直接跳到地址 `49A5E`。 - 表示“如果初始值为 0,就做初始化并提前退出”。 --- ### 6. 类型转换与减法(计算差值) ```asm cvtsi2ss xmm0, esi ``` - 将 32 位整数 `esi` 转换为单精度浮点数,存入 `xmm0`。 - 即 `(float)esi → xmm0` ```asm movss xmm1, [rbx+08] subss xmm1, xmm0 ``` - 加载当前值 → `xmm1` - 减去 `(float)esi` → 计算差值:`[rbx+08] - (float)esi` ```asm comiss xmm1, ["Tutorial-x86_64.exe"+26CD38] ``` - 比较差值是否接近零(再次与 epsilon 比较) ```asm jp "Tutorial-x86_64.exe"+499EA ; 若 NaN 跳转 jae "Tutorial-x86_64.exe"+499EA ; 若 >= epsilon 跳转 ``` - `jae` = Jump if Above or Equal(无符号比较),但在浮点中应谨慎使用。 - 实际上此处可能是误用;更常见的是 `jnb` 或结合 `comiss` 使用 `jbe/ja`。 - 总之:如果差值大于等于 epsilon,则跳转。 👉 **意图**:判断 `[rbx+08] ≈ (float)esi`?如果不是,就不更新。 --- ### 7. 设置默认值或修正 ```asm movss xmm0, ["Tutorial-x86_64.exe"+26CD38] ; 加载 epsilon (likely 0.0) jmp "Tutorial-x86_64.exe"+499ED ; 跳转到赋值点 ``` 然后: ```asm movaps xmm0, xmm1 movss [rbx+08], xmm0 ``` - 把计算后的差值 `xmm1` 写回 `[rbx+08]`。 - 即完成一次“平滑更新”或“衰减”。 > 🧠 推测:这是一个物理模拟或动画系统中的数值更新逻辑。 --- ### 8. 再次验证写入值是否为 0 ```asm comiss xmm0, ["Tutorial-x86_64.exe"+26CD38] jp "Tutorial-x86_64.exe"+49A0F jne "Tutorial-x86_64.exe"+49A0F ``` - 再次检测刚写入的值是否为 0。 - 是则继续,否则跳转。 ```asm mov rcx, [rbx+60] mov rdx, ["Tutorial-x86_64.exe"+1DE598] call "Tutorial-x86_64.exe"+B0420 jmp "Tutorial-x86_64.exe"+49A51 ``` - 若为 0,则调用另一个清理函数(释放资源?)。 - 然后跳过后续逻辑。 --- ### 9. 构造结构体并调用函数 ```asm movss xmm1, [rbx+08] lea rcx, [rbp-20] call "Tutorial-x86_64.exe"+674B0 ``` - 获取当前值 → `xmm1` - 取局部地址 `[rbp-20]` 作为输出缓冲区 - 调用函数 `674B0` —— 可能是格式化或序列化函数。 ```asm mov rax, [rbp-20] mov [rbp-10], rax ``` - 读取函数输出结果,并保存。 ```asm mov qword ptr [rbp-18], 0000000B lea r8, [rbp-18] mov rdx, ["Tutorial-x86_64.exe"+1DE5B8] xor r9, r9 lea rcx, [rbp-08] call "Tutorial-x86_64.exe"+65790 ``` - 构造参数调用另一个函数 `65790`,可能记录日志或发送事件。 ```asm mov rdx, [rbp-08] mov rcx, [rbx+60] call "Tutorial-x86_64.exe"+B0420 ``` - 再次调用 `B0420`,但这次带新参数。 ```asm mov ecx, 004C4B40 call "Tutorial-x86_64.exe"+104A0 mov [rbx+10], eax ``` - 调用 `104A0` 函数,传入立即数 `0x4C4B40`(可能是 ID 或类型码) - 返回值 `eax` 写入对象偏移 `+10` 处 → 更新状态字段。 ```asm nop ``` - 对齐或填充。 --- ### 10. 统一出口 ```asm exit: jmp returnhere ``` - 所有逻辑结束后统一跳回原程序继续执行。 --- ### 11. 注入点设置 ```asm "Tutorial-x86_64.exe"+4997F: jmp newmem nop DB ``` - 在原始地址 `4997F` 插入跳转到 `newmem`。 - `nop DB` 是 Cheat Engine 特殊语法,表示补一个字节的 `nop`(即 `db 0x90`)。 - 因为 `jmp rel32` 占 5 字节,而原指令可能不足 5 字节,需要用 `nop` 填充防止覆盖。 > ✅ 正确做法:确认原指令总长 ≥5 字节,否则需使用 AOB 注入并动态计算 patch size。 --- ### 12. 返回点 ```asm returnhere: ``` - 表示从 `newmem` 执行完毕后回到此处。 - 程序从此处继续运行原始代码流(后面的指令)。 --- ## 📌 总结:这段代码的整体功能推测 这是一个 **对象属性更新函数**,主要作用是: > **更新对象 `[rbx+08]` 处的浮点值(如坐标、速度等),进行平滑插值或衰减处理,并根据变化触发事件或资源管理。** ### 主要流程: 1. 保存上下文(寄存器、栈帧) 2. 检查目标值是否为 0 → 是则初始化 3. 计算当前值与输入参数的差值 4. 如果差值显著,则更新值 5. 若更新后为 0,触发资源释放 6. 否则记录变更事件 7. 最终更新状态码到 `[rbx+10]` 👉 **典型应用场景**:游戏中的角色移动、血量衰减、UI 动画过渡等。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值