x86 64架构汇编语言

10、结构体与宏

1、课程目标

  1. 掌握MASM中结构体的定义和使用
  2. 理解结构体的内存布局和对齐
  3. 学会宏的定义和使用方法
  4. 掌握条件汇编指令

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、课后作业

  1. 结构体练习:定义一个表示PE文件头的结构体
  2. 宏实战:编写一个安全的内存操作宏库
  3. 条件汇编:实现一个支持多平台的代码框架
  4. 结构体数组:创建并遍历结构体数组