1、课程目标
- 掌握MASM中结构体的定义和使用
- 理解结构体的内存布局和对齐
- 学会宏的定义和使用方法
- 掌握条件汇编指令
2、名词解释
| 术语 |
英文 |
说明 |
| 结构体 |
STRUCT |
将多个数据项组合成一个整体 |
| 内存对齐 |
Memory Alignment |
数据按特定边界存储以提高访问效率 |
| 宏 |
MACRO |
可重用的代码模板 |
| 条件汇编 |
Conditional Assembly |
根据条件决定是否包含某段代码 |
| 重复块 |
REPEAT Block |
重复生成代码的指令 |
3、使用工具
| 工具 |
用途 |
| MASM |
编写结构体和宏 |
| OFFSETOF宏 |
计算成员偏移量 |
4、技术原理
4.1、结构体内存布局
结构体成员按声明顺序存储,可能存在填充字节以满足对齐要求。
4.2、宏与函数的区别
| 特性 |
宏 |
函数 |
| 展开时机 |
汇编时 |
运行时 |
| 代码质量 |
内联展开 |
函数调用 |
| 开销 |
无调用开销 |
有调用开销 |
| 调试 |
较难调试 |
易于调试 |
5、代码实现
5.1、示例1:结构体定义与使用
.386
.model flat, stdcall
option casemap:none
; 定义结构体
POINT STRUCT
x DWORD ?
y DWORD ?
POINT ENDS
RECT STRUCT
left DWORD ?
top DWORD ?
right DWORD ?
bottom DWORD ?
RECT ENDS
; 嵌套结构体
PLAYER STRUCT
name BYTE 32 DUP(?)
health DWORD ?
pos POINT <> ; 嵌套POINT结构体
score DWORD ?
PLAYER ENDS
.data
; 结构体实例
pt1 POINT <100, 200>
pt2 POINT <0, 0>
rc RECT <0, 0, 640, 480>
player1 PLAYER <"Hero", 100, <50, 50>, 0>
.code
main PROC
; 访问结构体成员 - 直接方式
mov eax, pt1.x ; 获取x值
mov ebx, pt1.y ; 获取y值
; 修改结构体成员
mov pt1.x, 300
mov pt1.y, 400
; 通过指针访问结构体
lea esi, player1
mov eax, [esi + PLAYER.health] ; 获取health
mov ebx, [esi + PLAYER.pos.x] ; 获取嵌套结构体成员
; 修改通过指针
mov [esi + PLAYER.score], 1000
; ASSUME指令简化访问
ASSUME esi:PTR PLAYER
mov eax, [esi].health
mov ebx, [esi].pos.x
ASSUME esi:NOTHING
ret
main ENDP
END main
5.2、示例2:基本宏定义
.386
.model flat, stdcall
option casemap:none
; 无参数宏
SaveRegs MACRO
push eax
push ebx
push ecx
push edx
push esi
push edi
ENDM
RestoreRegs MACRO
pop edi
pop esi
pop edx
pop ecx
pop ebx
pop eax
ENDM
; 有参数宏
mPrint MACRO text
.data
@@str BYTE text, 0
.code
push OFFSET @@str
call printf
add esp, 4
ENDM
; 多参数宏
mAdd MACRO dest, src1, src2
mov eax, src1
add eax, src2
mov dest, eax
ENDM
; 带默认值的宏
mAlloc MACRO size:=<4096>
push size
call malloc
add esp, 4
ENDM
.code
main PROC
; 使用宏
SaveRegs
mAdd ebx, 100, 200 ; ebx = 100 + 200
mAlloc ; 使用默认值4096
mAlloc 8192 ; 指定大小
RestoreRegs
ret
main ENDP
END main
5.3、示例3:条件汇编
.386
.model flat, stdcall
option casemap:none
; 定义符号
DEBUG_MODE = 1
X64_BUILD = 0
; 根据平台选择代码
IF X64_BUILD
; 64位代码
POINTER_SIZE = 8
ELSE
; 32位代码
POINTER_SIZE = 4
ENDIF
; 调试宏
mDebug MACRO msg
IF DEBUG_MODE
push OFFSET msg
call OutputDebugStringA
ENDIF
ENDM
; 检查符号是否定义
IFDEF DEBUG_MODE
DEBUG_BUILD = 1
ELSE
DEBUG_BUILD = 0
ENDIF
; 比较值
IF POINTER_SIZE EQ 4
REG_SIZE = 4
ELSEIF POINTER_SIZE EQ 8
REG_SIZE = 8
ENDIF
.data
debugMsg BYTE "Debug: Function called", 0
.code
main PROC
mDebug debugMsg ; 仅在DEBUG_MODE=1时包含
ret
main ENDP
END main
5.4、示例4:重复块与循环
.386
.model flat, stdcall
option casemap:none
.data
; REPEAT重复生成
counter = 0
REPEAT 10
BYTE counter
counter = counter + 1
ENDM
; WHILE条件重复
value = 1
powers LABEL DWORD
WHILE value LT 1000
DWORD value
value = value * 2
ENDM
; FOR类似的遍历
names LABEL BYTE
FOR item, <"Alice", "Bob", "Charlie">
BYTE item, 0
ENDM
.code
; 生成多个相似函数的宏
mMakeGetter MACRO structName, fieldName, fieldType
Get&structName&&fieldName PROC pStruct:DWORD
mov eax, pStruct
IF fieldType EQ 1 ; BYTE
movzx eax, BYTE PTR [eax + structName.fieldName]
ELSEIF fieldType EQ 2 ; WORD
movzx eax, WORD PTR [eax + structName.fieldName]
ELSE ; DWORD
mov eax, [eax + structName.fieldName]
ENDIF
ret
Get&structName&&fieldName ENDP
ENDM
main PROC
ret
main ENDP
END main
5.5、示例5:Windows结构体实战
#include <windows.h>
#include <stdio.h>
// 汇编中处理Windows结构体
void ProcessModuleInfo() {
MODULEINFO mi = {0};
HMODULE hMod = GetModuleHandle(NULL);
// 内联汇编访问结构体
__asm {
lea eax, mi
; 获取结构体大小
mov ecx, SIZEOF MODULEINFO
; 计算成员偏移
mov edx, OFFSET MODULEINFO.SizeOfImage
}
GetModuleInformation(GetCurrentProcess(), hMod, &mi, sizeof(mi));
DWORD baseAddr, imageSize;
__asm {
lea esi, mi
mov eax, [esi] ; lpBaseOfDll
mov baseAddr, eax
mov eax, [esi + 4] ; SizeOfImage
mov imageSize, eax
}
printf("Base: 0x%08X, Size: 0x%X\n", baseAddr, imageSize);
}
// 定义自己的结构体并用汇编操作
#pragma pack(push, 1) // 1字节对齐
typedef struct {
BYTE opcode; // 偏移0
DWORD offset; // 偏移1
} JMP_INSTRUCTION;
#pragma pack(pop)
void CreateJmpInstruction(JMP_INSTRUCTION* jmp, DWORD target, DWORD current) {
__asm {
mov edi, jmp
mov BYTE PTR [edi], 0E9h ; JMP opcode
mov eax, target
sub eax, current
sub eax, 5 ; 指令长度
mov [edi + 1], eax ; 相对偏移
}
}
int main() {
ProcessModuleInfo();
JMP_INSTRUCTION jmp;
CreateJmpInstruction(&jmp, 0x12345678, 0x10000000);
printf("JMP: %02X %08X\n", jmp.opcode, jmp.offset);
return 0;
}
6、课后作业
- 结构体练习:定义一个表示PE文件头的结构体
- 宏实战:编写一个安全的内存操作宏库
- 条件汇编:实现一个支持多平台的代码框架
- 结构体数组:创建并遍历结构体数组