板凳----------(枯藤 )vs2026+win10(第五章-3)

bash 复制代码
INCLUDE Irvine32.inc

; ========== Windows API 常量 ==========
GENERIC_WRITE       EQU 40000000h
OPEN_ALWAYS         EQU 4
FILE_ATTRIBUTE_NORMAL EQU 80h
FILE_END            EQU 2
INVALID_HANDLE_VALUE EQU -1

.data
    ; 文件路径
    fileName1 BYTE "test1.txt",0
    fileName2 BYTE "test2.txt",0
    
    ; 内容 - 第一次写入
    content1  BYTE "[第一次写入] Hello 豆包!",0dh,0ah
    len1 DWORD ($ - OFFSET content1)
    
    content2  BYTE "[第一次写入] 测试2内容",0dh,0ah
    len2 DWORD ($ - OFFSET content2)
    
    ; 内容 - 第二次追加
    append1   BYTE "[第二次追加] 这是追加的内容!",0dh,0ah
    alen1 DWORD ($ - OFFSET append1)
    
    append2   BYTE "[第二次追加] 再次追加成功!",0dh,0ah
    alen2 DWORD ($ - OFFSET append2)
    
    ; 消息
    msgCreate BYTE "创建文件:",0
    msgAppend BYTE "追加内容:",0
    msgSucc   BYTE "? 成功",0dh,0ah,0
    msgFail   BYTE "? 失败",0dh,0ah,0
    completeMsg BYTE 0dh,0ah,"=== 操作完成 ===",0dh,0ah,0
    pathMsg     BYTE "文件位置:",0
    pathBuffer  BYTE 260 DUP(0)

.code

; ============================================================
; 过程:SimpleAppendFile
; 功能:简单的追加写入文件
; 输入:filename - 文件名地址
;       dataptr  - 数据地址
;       datalen  - 数据长度
; ============================================================
SimpleAppendFile PROC USES ebx ecx edx esi edi,
    filename:PTR BYTE,
    dataptr:PTR BYTE,
    datalen:DWORD
    
    LOCAL hFile:DWORD
    LOCAL bytesWritten:DWORD
    
    ; 使用Irvine32的OpenInputFile尝试打开现有文件
    mov edx, filename
    call OpenInputFile
    
    .IF eax == INVALID_HANDLE_VALUE
        ; 文件不存在,创建新文件
        call CreateOutputFile
    .ELSE
        ; 文件存在,保持打开状态
        mov hFile, eax
        
        ; 移动文件指针到末尾
        push FILE_END
        push 0
        push 0
        push hFile
        call SetFilePointer
        
        ; 写入数据
        mov eax, hFile
        mov edx, dataptr
        mov ecx, datalen
        call WriteToFile
        
        ; 关闭文件
        mov eax, hFile
        call CloseFile
        
        mov eax, 1  ; 成功
        ret
    .ENDIF
    
    .IF eax == INVALID_HANDLE_VALUE
        mov eax, 0  ; 失败
        ret
    .ENDIF
    
    mov hFile, eax
    
    ; 写入数据到新文件
    mov edx, dataptr
    mov ecx, datalen
    call WriteToFile
    
    ; 关闭文件
    mov eax, hFile
    call CloseFile
    
    mov eax, 1  ; 成功
    ret
SimpleAppendFile ENDP

; ============================================================
; 主程序 - 简化版本
; ============================================================
main PROC
    call Crlf
    
    ; ========== 测试1:创建并追加 ==========
    mov edx, OFFSET fileName1
    call WriteString
    mov edx, OFFSET msgCreate
    call WriteString
    
    ; 第一次写入
    INVOKE SimpleAppendFile,
        OFFSET fileName1,
        OFFSET content1,
        len1
    
    .IF eax == 1
        mov edx, OFFSET msgSucc
        call WriteString
    .ELSE
        mov edx, OFFSET msgFail
        call WriteString
    .ENDIF
    
    ; 第二次追加
    mov edx, OFFSET fileName1
    call WriteString
    mov edx, OFFSET msgAppend
    call WriteString
    
    INVOKE SimpleAppendFile,
        OFFSET fileName1,
        OFFSET append1,
        alen1
    
    .IF eax == 1
        mov edx, OFFSET msgSucc
        call WriteString
    .ELSE
        mov edx, OFFSET msgFail
        call WriteString
    .ENDIF
    
    call Crlf
    
    ; ========== 测试2:创建并追加 ==========
    mov edx, OFFSET fileName2
    call WriteString
    mov edx, OFFSET msgCreate
    call WriteString
    
    ; 第一次写入
    INVOKE SimpleAppendFile,
        OFFSET fileName2,
        OFFSET content2,
        len2
    
    .IF eax == 1
        mov edx, OFFSET msgSucc
        call WriteString
    .ELSE
        mov edx, OFFSET msgFail
        call WriteString
    .ENDIF
    
    ; 第二次追加
    mov edx, OFFSET fileName2
    call WriteString
    mov edx, OFFSET msgAppend
    call WriteString
    
    INVOKE SimpleAppendFile,
        OFFSET fileName2,
        OFFSET append2,
        alen2
    
    .IF eax == 1
        mov edx, OFFSET msgSucc
        call WriteString
    .ELSE
        mov edx, OFFSET msgFail
        call WriteString
    .ENDIF
    
    ; ========== 显示完成信息 ==========
    mov edx, OFFSET completeMsg
    call WriteString
    
    ; 显示当前目录
    mov edx, OFFSET pathMsg
    call WriteString
    
    ; 使用Irvine32的GetCurrentDirectory
    mov edx, OFFSET pathBuffer
    mov ecx, SIZEOF pathBuffer
    call GetCurrentDirectory
    
    mov edx, OFFSET pathBuffer
    call WriteString
    call Crlf
    
    ; 显示文件内容预览
    mov edx, OFFSET previewMsg
    call WriteString
    call Crlf
    
    ; 读取并显示test1.txt内容
    mov edx, OFFSET fileName1
    call OpenInputFile
    .IF eax != INVALID_HANDLE_VALUE
        mov ebx, eax  ; 保存句柄
        
        ; 读取文件内容
        mov edx, OFFSET readBuffer
        mov ecx, SIZEOF readBuffer
        call ReadFromFile
        
        ; 显示内容
        mov edx, OFFSET fileName1
        call WriteString
        mov edx, OFFSET colonMsg
        call WriteString
        call Crlf
        
        mov edx, OFFSET readBuffer
        call WriteString
        call Crlf
        
        ; 关闭文件
        mov eax, ebx
        call CloseFile
    .ENDIF
    
    call WaitMsg
    exit
    
    ; 数据
    previewMsg BYTE 0dh,0ah,"=== 文件内容预览 ===",0dh,0ah,0
    colonMsg   BYTE " 的内容:",0
    readBuffer BYTE 1000 DUP(?)
    
main ENDP
END main
bash 复制代码
INCLUDE Irvine32.inc

.data
COUNT = 4  ; 调试:先改为1次循环,减少输入等待(如需多轮再改回4)
BlueTextOnGray = blue + (lightGray*16)  ; 前景蓝+背景浅灰(正确)
DefaultColor = lightGray + (black*16)   ; 恢复默认颜色

arrayD SDWORD 12345678h, 1A4B2000h, 3434h, 7AB9h
prompt BYTE "Enter a 32-bit signed integer:", 0dh, 0ah, 0  ; 加换行,排版更清晰
msgMem BYTE 0dh,0ah,"===== 数组内存内容(DumpMem) =====",0dh,0ah,0
msgInputTitle BYTE "===== 你输入的数值(十进制/十六进制/二进制) =====",0dh,0ah,0

.code
main PROC
    ; ========== 1. 初始化控制台(颜色+清屏) ==========
    mov eax, BlueTextOnGray
    call SetTextColor
    call Clrscr              ; 清屏(只在开头执行一次)

    ; ========== 2. 打印数组内存(DumpMem)- 核心调试输出 ==========
    mov edx, OFFSET msgMem   ; 先显示标题,方便识别
    call WriteString
    mov esi, OFFSET arrayD   ; 数组首地址
    mov ebx, TYPE arrayD     ; 每个元素4字节(SDWORD)
    mov ecx, LENGTHOF arrayD ; 4个元素
    call DumpMem             ; 打印数组内存(一次性输出,无需等待)

    ; ========== 3. 仅1次输入循环(调试重点:减少等待) ==========
    mov ecx, COUNT           ; 循环次数=1(原4次导致等待过长)
L1:
    ; 显示输入提示
    mov edx, OFFSET msgInputTitle
    call WriteString
    mov edx, OFFSET prompt
    call WriteString
    call ReadInt             ; 等待用户输入1个整数(仅1次)
    call Crlf

    ; 打印输入值的三种格式(十进制/十六进制/二进制)
    call WriteInt            ; 十进制
    call Crlf
    call WriteHex            ; 十六进制
    call Crlf
    call WriteBin            ; 二进制
    call Crlf
    call Crlf

    Loop L1  ; 仅循环1次,立即结束

    ; ========== 4. 暂停查看输出(仅1次按键等待) ==========
    call WaitMsg             ; 显示「Press any key to continue...」,按任意键退出
    ; 【调试关键】注释掉最后的清屏,保留输出内容
    ; mov eax, DefaultColor
    ; call SetTextColor
    ; call Clrscr  ; 删掉清屏!否则会清空所有输出

    ; ========== 5. 安全退出 ==========
    INVOKE ExitProcess, 0
main ENDP
END main

========================================
===== 数组内存内容(DumpMem) =====

Dump of offset 00406000
-------------------------------
12345678  1A4B2000  00003434  00007AB9
===== 你输入的数值(十进制/十六进制/二进制) =====
Enter a 32-bit signed integer:
34525

+34525
000086DD
0000 0000 0000 0000 1000 0110 1101 1101

===== 你输入的数值(十进制/十六进制/二进制) =====
Enter a 32-bit signed integer:
1000110

+1000110
000F42AE
0000 0000 0000 1111 0100 0010 1010 1110

===== 你输入的数值(十进制/十六进制/二进制) =====
Enter a 32-bit signed integer:
bec11
 <invalid integer>

+0
00000000
0000 0000 0000 0000 0000 0000 0000 0000

===== 你输入的数值(十进制/十六进制/二进制) =====
Enter a 32-bit signed integer:
-45

-45
FFFFFFD3
1111 1111 1111 1111 1111 1111 1101 0011

Press any key to continue...
C:\Users\lenovo\Desktop\Project32\Debug\Project32.exe (进程 22148)已退出,代码为 0 (0x0)。
要在调试停止时自动关闭控制台,请启用"工具"->"选项"->"调试"->"调试停止时自动关闭控制台"。
按任意键关闭此窗口. . .

可以从调试优化、功能扩展、交互增强、格式美化四个维度升级,先修复基础调试问题,再给出多维度扩展方案:

一、基础调试优化(解决输出排版 / 稳定性问题)

原代码运行正常,但输出可能挤在一起,且无暂停逻辑导致控制台一闪而过。先优化基础体验:

bash 复制代码
asm
; 链接库测试#2 - 调试优化版
INCLUDE Irvine32.inc

TAB = 9       ; Tab的ASCII码
.data
    ; 新增提示标题,增强可读性
    msgRand1 BYTE "===== Rand1:10个无符号伪随机整数(0~2^32-1) =====",0dh,0ah,0
    msgRand2 BYTE "===== Rand2:10个有符号伪随机整数(-50~+49) =====",0dh,0ah,0
    msgEnd   BYTE 0dh,0ah,"按任意键退出...",0

.code
main PROC
    call Randomize               ; 初始化随机生成器(仅需1次,放在最开头)
    
    ; 输出Rand1标题 + 执行Rand1
    mov edx, OFFSET msgRand1
    call WriteString
    call Rand1

    ; 输出Rand2标题 + 执行Rand2
    mov edx, OFFSET msgRand2
    call WriteString
    call Rand2

    ; 调试关键:暂停控制台,避免一闪而过
    mov edx, OFFSET msgEnd
    call WriteString
    call WaitMsg

    exit
main ENDP

Rand1 PROC
    ; 生成10个伪随机整数(无符号)
    push ecx                     ; 保护寄存器(调试规范)
    mov ecx, 10                  ; 循环10次
L1:
    call Random32                ; 生成32位无符号随机数
    call WriteDec                ; 无符号十进制输出
    mov al, TAB
    call WriteChar               ; 制表符分隔
    loop L1
    call Crlf
    pop ecx
    ret
Rand1 ENDP

Rand2 PROC
    ; 在-50到+49之间生成10个伪随机整数
    push ecx                     ; 保护寄存器(调试规范)
    mov ecx, 10                  ; 循环10次
L2:                              ; 标签重命名,避免和Rand1的L1冲突(调试防错)
    mov eax, 100                 ; 0~99
    call RandomRange             ; 生成0~99的随机数
    sub eax, 50                  ; 偏移为-50~+49
    call WriteInt                ; 有符号十进制输出
    mov al, TAB
    call WriteChar
    loop L2
    call Crlf
    pop ecx
    ret
Rand2 ENDP
END main

===== Rand1:10个无符号伪随机整数(0~2^32-1) =====
2550515389      4226938565      3523791352      1299601020      3691712157      798653749       907172987       566979973       1615109587      2744685220
===== Rand2:10个有符号伪随机整数(-50~+49) =====
-38     +42     +24     +30     +7      +31     -4      -10     +15     -21

按任意键退出...Press any key to continue...

二、基础调试说明(核心优化点)

  1. 寄存器保护:Rand1/Rand2中用push ecx/pop ecx保护寄存器(调试时避免寄存器值被覆盖);
  2. 标签唯一性:Rand2 的循环标签改为L2,避免和 Rand1 的L1冲突(汇编中标签全局有效,重复会编译错误);
  3. 暂停逻辑:新增WaitMsg,控制台不会一闪而过,方便查看输出;
  4. 标题分隔:新增提示文本,清晰区分两个随机数函数的输出。
    三、功能扩展方案(在此基础上可做的优化)
  5. 扩展 1:自定义随机数范围(封装成通用函数)
bash 复制代码
新增RandRange通用函数,支持任意范围的有符号 / 无符号随机数:
asm
; 新增:通用随机数生成函数
; 参数:EAX=最大值,EBX=最小值(有符号)
; 返回:EAX=min~max之间的随机数
RandCustom PROC USES ecx edx
    push ebx
    sub eax, ebx        ; 计算范围长度(max - min)
    call RandomRange    ; 生成0~(max-min)的随机数
    add eax, ebx        ; 偏移到min~max
    pop ebx
    ret
RandCustom ENDP

; 在main中调用示例:
; 生成10个 100~200 的无符号随机数
mov edx, OFFSET msgRand3
call WriteString
mov ecx,10
L3:
mov eax,200
mov ebx,100
call RandCustom
call WriteDec
mov al,TAB
call WriteChar
loop L3
call Crlf
  1. 扩展 2:输出十六进制 / 二进制格式(多格式展示)
bash 复制代码
在 Rand1/Rand2 中新增进制输出,对比不同格式的随机数:
asm
; 改造Rand1,新增十六进制/二进制输出
Rand1 PROC
    push ecx
    mov ecx, 10
L1:
    call Random32
    call WriteDec        ; 十进制
    mov al, TAB
    call WriteChar
    call WriteHex        ; 十六进制
    mov al, TAB
    call WriteChar
    call WriteBin        ; 二进制
    call Crlf            ; 每行一个数,避免拥挤
    loop L1
    pop ecx
    ret
Rand1 ENDP
  1. 扩展 3:将随机数存入数组(持久化 + 统计)
bash 复制代码
生成随机数后存入数组,再计算最大值 / 最小值 / 平均值:
asm
.data
    arrayRand2 SDWORD 10 DUP(0)  ; 存储Rand2的10个随机数
    msgStat    BYTE "===== Rand2 统计结果 =====",0dh,0ah,0
    msgMax     BYTE "最大值:",0
    msgMin     BYTE "最小值:",0
    msgAvg     BYTE "平均值:",0

.code
; 改造Rand2,将随机数存入数组
Rand2 PROC
    push ecx esi
    mov ecx,10
    mov esi, OFFSET arrayRand2
L2:
    mov eax,100
    call RandomRange
    sub eax,50
    mov [esi], eax       ; 存入数组
    call WriteInt
    mov al,TAB
    call WriteChar
    add esi, TYPE arrayRand2
    loop L2
    call Crlf
    pop esi ecx
    ret
Rand2 ENDP

; 新增:统计数组最大值/最小值/平均值
StatArray PROC USES ecx esi eax ebx edx
    mov esi, OFFSET arrayRand2
    mov ecx, LENGTHOF arrayRand2
    mov eax, [esi]       ; 初始化最大值/最小值
    mov ebx, eax
    mov edx, 0           ; 总和

L4:
    cmp [esi], eax
    jg UpdateMax
    cmp [esi], ebx
    jl UpdateMin
    jmp Next

UpdateMax:
    mov eax, [esi]
    jmp Next
UpdateMin:
    mov ebx, [esi]
Next:
    add edx, [esi]
    add esi, TYPE arrayRand2
    loop L4

    ; 输出统计结果
    mov edx, OFFSET msgMax
    call WriteString
    call WriteInt
    call Crlf

    mov edx, OFFSET msgMin
    call WriteString
    mov eax, ebx
    call WriteInt
    call Crlf

    mov edx, OFFSET msgAvg
    call WriteString
    mov eax, edx
    cdq
    mov ebx, LENGTHOF arrayRand2
    idiv ebx             ; 有符号除法计算平均值
    call WriteInt
    call Crlf

    ret
StatArray ENDP
  1. 扩展 4:控制台颜色美化(区分不同输出)
bash 复制代码
用SetTextColor给不同函数的输出设置不同颜色,调试更直观:
asm
; 在main中调用Rand1前设置颜色
mov eax, green + (black*16)  ; 绿色字体,黑色背景
call SetTextColor
mov edx, OFFSET msgRand1
call WriteString
call Rand1

; 调用Rand2前切换颜色
mov eax, blue + (black*16)
call SetTextColor
mov edx, OFFSET msgRand2
call WriteString
call Rand2

; 统计结果用红色
mov eax, red + (black*16)
call SetTextColor
mov edx, OFFSET msgStat
call WriteString
call StatArray
  1. 扩展 5:循环次数可配置(动态输入)
bash 复制代码
让用户输入循环次数,而非固定 10 次,增强交互性:
asm
.data
    msgInputCount BYTE "请输入随机数生成个数:",0
    inputCount DWORD ?

.code
; 在main中新增:
mov edx, OFFSET msgInputCount
call WriteString
call ReadInt
mov inputCount, eax

; 改造Rand1/Rand2的循环次数:
Rand1 PROC
    push ecx
    mov ecx, inputCount  ; 用用户输入的次数替代固定10
    ; 后续逻辑不变
Rand1 ENDP

四、调试技巧(排查问题用)

  1. 断点调试:在Random32/RandomRange处打断点,查看 EAX 寄存器的值,确认随机数生成是否正常;
  2. 寄存器监控:调试时查看 ECX(循环次数)、ESI(数组地址)的变化,确认循环和数组操作是否正确;
  3. 边界测试:测试 Rand2 的边界值(-50、+49)是否能生成,验证RandomRange+sub eax,50的逻辑正确性;
  4. 重复运行测试:多次运行程序,确认Randomize初始化生效(每次生成的随机数不同)。
    五、最终扩展版完整代码(含所有优化)
bash 复制代码
INCLUDE Irvine32.inc

TAB = 9
.data
    msgRand1        BYTE "===== Rand1:无符号伪随机整数(0~2^32-1) =====",0dh,0ah,0
    msgRand2        BYTE "===== Rand2:有符号伪随机整数(-50~+49) =====",0dh,0ah,0
    msgRand3        BYTE "===== Rand3:自定义范围随机数(100~200) =====",0dh,0ah,0
    msgStat         BYTE "===== Rand2 统计结果(最大值/最小值/平均值) =====",0dh,0ah,0
    msgInputCount   BYTE "请输入随机数生成个数:",0
    msgEnd          BYTE 0dh,0ah,"按任意键退出...",0
    arrayRand2      SDWORD 100 DUP(0)  ; 扩大数组容量
    inputCount      DWORD ?
    msgMax          BYTE "最大值:",0
    msgMin          BYTE "最小值:",0
    msgAvg          BYTE "平均值:",0

.code
main PROC
    call Randomize               ; 初始化随机生成器
    call Clrscr                  ; 清屏

    ; 1. 输入生成个数
    mov eax, white + (black * 16)  ; 修正:添加*运算符,避免表达式错误
    call SetTextColor
    mov edx, OFFSET msgInputCount
    call WriteString
    call ReadInt
    mov inputCount, eax
    call Crlf

    ; 2. 生成无符号随机数(绿色)
    mov eax, green + (black * 16)   ; 修正:添加*运算符
    call SetTextColor
    mov edx, OFFSET msgRand1
    call WriteString
    call Rand1
    call Crlf

    ; 3. 生成有符号随机数(蓝色)+ 存入数组
    mov eax, blue + (black * 16)    ; 修正:添加*运算符
    call SetTextColor
    mov edx, OFFSET msgRand2
    call WriteString
    call Rand2
    call Crlf

    ; 4. 自定义范围随机数(黄色)
    mov eax, yellow + (black * 16)  ; 修正:添加*运算符
    call SetTextColor
    mov edx, OFFSET msgRand3
    call WriteString
    call RandCustomTest
    call Crlf

    ; 5. 统计Rand2结果(红色)
    mov eax, red + (black * 16)     ; 修正:添加*运算符
    call SetTextColor
    mov edx, OFFSET msgStat
    call WriteString
    call StatArray

    ; 6. 暂停退出
    mov eax, white + (black * 16)
    call SetTextColor
    mov edx, OFFSET msgEnd
    call WriteString
    call WaitMsg

    exit
main ENDP

; 无符号随机数生成
Rand1 PROC
    push ecx
    mov ecx, inputCount
L1:
    call Random32
    call WriteDec
    mov al, TAB
    call WriteChar
    loop L1
    call Crlf
    pop ecx
    ret
Rand1 ENDP

; 有符号随机数(-50~+49)+ 存入数组
Rand2 PROC
    push ecx
    push esi
    mov ecx, inputCount
    mov esi, OFFSET arrayRand2
L2:
    mov eax, 100
    call RandomRange
    sub eax, 50
    mov [esi], eax
    call WriteInt
    mov al, TAB
    call WriteChar
    add esi, TYPE arrayRand2
    loop L2
    call Crlf
    pop esi
    pop ecx
    ret
Rand2 ENDP

; 自定义范围随机数测试(100~200)
RandCustomTest PROC
    push ecx
    mov ecx, inputCount
L3:
    mov eax, 200
    mov ebx, 100
    call RandCustom       ; 调用通用随机数函数
    call WriteDec
    mov al, TAB
    call WriteChar
    loop L3
    call Crlf
    pop ecx
    ret
RandCustomTest ENDP

; 通用随机数生成函数(min~max)- 修正USES格式
RandCustom PROC
    ; 修正:放弃USES,手动push/pop寄存器(避免USES语法错误)
    push ebx
    push ecx
    push edx

    sub eax, ebx        ; 计算范围长度(max - min)
    call RandomRange    ; 生成0~(max-min)的随机数
    add eax, ebx        ; 偏移到min~max

    pop edx
    pop ecx
    pop ebx
    ret
RandCustom ENDP

; 统计数组最大值/最小值/平均值 - 修正表达式错误
StatArray PROC
    push ecx
    push esi
    push eax
    push ebx
    push edx

    mov esi, OFFSET arrayRand2
    mov ecx, inputCount
    mov eax, [esi]       ; 初始化最大值
    mov ebx, eax         ; 初始化最小值
    mov edx, 0           ; 总和

L4:
    ; 修正:表达式添加明确的运算符,避免A2206
    cmp DWORD PTR [esi], eax  ; 显式指定DWORD类型,避免表达式歧义
    jg UpdateMax
    cmp DWORD PTR [esi], ebx
    jl UpdateMin
    jmp Next

UpdateMax:
    mov eax, [esi]
    jmp Next
UpdateMin:
    mov ebx, [esi]
Next:
    add edx, [esi]
    add esi, TYPE arrayRand2
    loop L4

    ; 输出最大值
    mov edx, OFFSET msgMax
    call WriteString
    call WriteInt
    call Crlf

    ; 输出最小值
    mov edx, OFFSET msgMin
    call WriteString
    mov eax, ebx
    call WriteInt
    call Crlf

    ; 输出平均值
    mov edx, OFFSET msgAvg
    call WriteString
    mov eax, edx         ; 总和存入EAX
    cdq                 ; 扩展符号位
    mov ebx, inputCount
    idiv ebx            ; 有符号除法计算平均值
    call WriteInt
    call Crlf

    pop edx
    pop ebx
    pop eax
    pop esi
    pop ecx
    ret
StatArray ENDP
END main


请输入随机数生成个数:5

===== Rand1:无符号伪随机整数(0~2^32-1) =====
2181565443      1025734428      210630782       2681330772      3859736796

===== Rand2:有符号伪随机整数(-50~+49) =====
-30     +16     -35     +35     -24

===== Rand3:自定义范围随机数(100~200) =====
196     145     185     190     123

===== Rand2 统计结果(最大值/最小值/平均值) =====
最大值:+35
最小值:-35
平均值:+843908

总结

基础调试主要解决 "输出可见性、寄存器保护、标签冲突";扩展方向包括:

• 功能层:自定义范围、数组存储、统计分析;

• 交互层:动态输入、颜色区分;

• 调试层:断点监控、边界测试。

你可以根据需求逐步添加扩展功能,先从简单的 "颜色美化、自定义范围" 开始,再进阶到 "数组统计、动态输入"

bash 复制代码
; 链接库测试#3 - 
; 计算嵌套循环的执行时间
INCLUDE Irvine32.inc

.data
OUTER_LOOP_COUNT = 3       ; 外层循环次数
INNER_LOOP_COUNT = 1000000 ; 内循环次数(常量)
innerCountVal DWORD INNER_LOOP_COUNT ; 定义内存变量存储内循环次数(关键!)
startTime DWORD ?
msg1      BYTE "Please wait...", 0dh, 0ah, 0
msg2      BYTE "Elapsed milliseconds: ", 0
msg3      BYTE "Outer loop count: ", 0
msg4      BYTE "Inner loop count per outer: ", 0
msg5      BYTE "Total iterations: ", 0
msgEnd    BYTE 0dh,0ah,"按任意键退出...",0

.code
main PROC
    ; ========== 1. 初始化 + 显示提示 ==========
    call Clrscr
    mov edx, OFFSET msg1
    call WriteString

    ; ========== 2. 保存开始时间 ==========
    call GetMSeconds
    mov startTime, eax

    ; ========== 3. 外层循环 ==========
    mov ecx, OUTER_LOOP_COUNT
L1:
    call innerLoop
    loop L1

    ; ========== 4. 计算并显示执行时间 ==========
    call GetMSeconds
    sub eax, startTime          ; 总耗时 = 结束时间 - 开始时间

    ; 显示循环配置信息(修正mul立即数错误)
    call Crlf
    mov edx, OFFSET msg3
    call WriteString
    mov eax, OUTER_LOOP_COUNT
    call WriteDec
    call Crlf

    mov edx, OFFSET msg4
    call WriteString
    mov eax, INNER_LOOP_COUNT  ; 直接赋值常量到寄存器,避免mul立即数
    call WriteDec
    call Crlf

    ; 计算总迭代次数:外层×内层(修正核心错误)
    mov edx, OFFSET msg5
    call WriteString
    mov eax, OUTER_LOOP_COUNT
    mul innerCountVal          ; mul + 内存变量(合法),结果存EDX:EAX
    call WriteDec
    call Crlf

    ; 显示耗时
    mov edx, OFFSET msg2
    call WriteString
    mov eax, startTime         ; 恢复耗时值(注:此处需重新计算,避免覆盖)
    call GetMSeconds
    sub eax, startTime
    call WriteDec
    call Crlf

    ; ========== 5. 暂停退出 ==========
    mov edx, OFFSET msgEnd
    call WriteString
    call WaitMsg

    exit
main ENDP

; 内循环:使用内存变量,避免立即数
innerLoop PROC
    push ecx
    mov ecx, innerCountVal     ; 用内存变量替代直接常量(可选,更规范)
L1:
    mul eax                     ; 消耗CPU周期(mul eax 等价于 mul dword ptr [eax])
    loop L1

    pop ecx
    ret
innerLoop ENDP
END main
===========================
Please wait...

Outer loop count: 3
Inner loop count per outer: 1000000
Total iterations: 3000000
Elapsed milliseconds: 8

二、基础调试说明(核心优化点)

  1. 降低内循环次数:
    原0FFFFFFFFh(42 亿次)执行需数分钟,改为1000000(100 万次),几秒内完成,便于调试;
  2. 增加进度 / 配置反馈:
    显示外层 / 内循环次数、总迭代次数,明确程序执行的核心参数;
  3. 保留核心逻辑:
    仍用GetMSeconds统计时间,mul eax消耗 CPU 周期,符合 "测试嵌套循环执行时间" 的核心目标;
  4. 暂停退出:
    新增WaitMsg,避免控制台一闪而过,方便查看耗时结果。
    三、功能扩展方案(在此基础上可做的优化)
  5. 扩展 1:高精度计时(微秒级)
bash 复制代码
GetMSeconds是毫秒级,改用QueryPerformanceCounter实现微秒级计时,精度更高:
asm
; 新增数据段
.data
    freq        LARGE_INTEGER ?  ; 计数器频率
    startCount  LARGE_INTEGER ?
    endCount    LARGE_INTEGER ?
    elapsedUs   DWORD ?          ; 微秒数
    msg6        BYTE "Elapsed microseconds: ", 0

.code
main PROC
    ; 初始化高精度计数器
    INVOKE QueryPerformanceFrequency, ADDR freq
    INVOKE QueryPerformanceCounter, ADDR startCount

    ; 执行嵌套循环(原逻辑)
    mov ecx, OUTER_LOOP_COUNT
L1: call innerLoop
    loop L1

    ; 计算微秒级耗时
    INVOKE QueryPerformanceCounter, ADDR endCount
    ; 耗时 = (end - start) × 1000000 / freq
    mov eax, endCount.LowPart
    sub eax, startCount.LowPart
    mul DWORD PTR 1000000
    div freq.LowPart
    mov elapsedUs, eax

    ; 显示微秒数
    mov edx, OFFSET msg6
    call WriteString
    mov eax, elapsedUs
    call WriteDec
    call Crlf
    ; 后续逻辑不变
main ENDP
  1. 扩展 2:动态配置循环次数(用户输入)
bash 复制代码
让用户自定义外层 / 内循环次数,增强交互性:
asm
.data
    msgInputOuter BYTE "请输入外层循环次数:",0
    msgInputInner BYTE "请输入内循环次数:",0
    outerCount    DWORD ?
    innerCount    DWORD ?

.code
main PROC
    ; 输入外层循环次数
    mov edx, OFFSET msgInputOuter
    call WriteString
    call ReadInt
    mov outerCount, eax

    ; 输入内循环次数
    mov edx, OFFSET msgInputInner
    call WriteString
    call ReadInt
    mov innerCount, eax

    ; 执行外层循环
    mov ecx, outerCount
L1: call innerLoop
    loop L1

; 改造innerLoop
innerLoop PROC
    push ecx
    mov ecx, innerCount  ; 用用户输入的内循环次数
L1: mul eax
    loop L1
    pop ecx
    ret
innerLoop ENDP
  1. 扩展 3:对比不同指令的执行耗时
bash 复制代码
测试不同 CPU 指令(如add/mul/div)的耗时,分析指令性能:
asm
.data
    msgMulTime BYTE "Mul指令耗时(ms):",0
    msgAddTime BYTE "Add指令耗时(ms):",0

.code
main PROC
    ; 测试mul指令耗时
    call GetMSeconds
    mov startTime, eax
    mov ecx, OUTER_LOOP_COUNT
L1: call innerLoop_Mul
    loop L1
    call GetMSeconds
    sub eax, startTime
    mov edx, OFFSET msgMulTime
    call WriteString
    call WriteDec
    call Crlf

    ; 测试add指令耗时
    call GetMSeconds
    mov startTime, eax
    mov ecx, OUTER_LOOP_COUNT
L2: call innerLoop_Add
    loop L2
    call GetMSeconds
    sub eax, startTime
    mov edx, OFFSET msgAddTime
    call WriteString
    call WriteDec
    call Crlf

; 新增innerLoop_Add
innerLoop_Add PROC
    push ecx
    mov ecx, INNER_LOOP_COUNT
L1: add eax, eax  ; 替换mul为add
    loop L1
    pop ecx
    ret
innerLoop_Add ENDP
  1. 扩展 4:显示 CPU 核心数 / 频率(调试上下文)
bash 复制代码
获取系统 CPU 信息,让耗时分析更有参考性:
asm
.data
    msgCPUFreq BYTE "CPU频率(MHz):",0
    cpuFreq    DWORD ?

.code
main PROC
    ; 获取CPU频率(简化版,实际需调用WinAPI)
    mov eax, 3000  ; 模拟3GHz(3000MHz),可替换为真实API调用
    mov cpuFreq, eax
    mov edx, OFFSET msgCPUFreq
    call WriteString
    call WriteDec
    call Crlf

    ; 后续逻辑不变
  1. 扩展 5:循环优化对比(普通 loop vs 手动跳转)
bash 复制代码
loop指令效率较低,对比 "手动跳转 + ecx 自减" 的耗时:
asm
; 改造innerLoop,手动实现循环
innerLoop_Manual PROC
    push ecx
    mov ecx, INNER_LOOP_COUNT
L1: mul eax
    dec ecx       ; 手动减ecx
    jnz L1        ; 手动跳转
    pop ecx
    ret
innerLoop_Manual ENDP

; 对比耗时
.data
    msgLoopTime BYTE "Loop指令耗时(ms):",0
    msgManualTime BYTE "手动跳转耗时(ms):",0

.code
main PROC
    ; 测试loop指令
    call GetMSeconds
    mov startTime, eax
    mov ecx, OUTER_LOOP_COUNT
L1: call innerLoop
    loop L1
    call GetMSeconds
    sub eax, startTime
    mov edx, OFFSET msgLoopTime
    call WriteString
    call WriteDec
    call Crlf

    ; 测试手动跳转
    call GetMSeconds
    mov startTime, eax
    mov ecx, OUTER_LOOP_COUNT
L2: call innerLoop_Manual
    loop L2
    call GetMSeconds
    sub eax, startTime
    mov edx, OFFSET msgManualTime
    call WriteString
    call WriteDec
    call Crlf

四、调试技巧(排查耗时异常)

  1. 断点调试:
    o 在GetMSeconds处打断点,查看eax的值,确认时间戳正确;
    o 在innerLoop的L1标签处打断点,查看ecx的递减过程,确认循环次数正确;
  2. 寄存器监控:
    调试时观察ecx(循环计数器)、eax(时间戳 / 指令操作数)的变化,排查循环死循环 / 次数错误;
  3. 性能分析:
    o 若耗时为 0,说明循环次数太少,适当增大INNER_LOOP_COUNT;
    o 若耗时异常大,检查是否有死循环(如ecx未正确push/pop);
  4. 对比测试:
    多次运行程序,对比耗时是否稳定(CPU 调度可能导致耗时小幅波动)。
    五、最终扩展版完整代码(含高精度计时 + 指令对比)
bash 复制代码
; 链接库测试#3 - 兼容Irvine32的最终版
; 计算嵌套循环执行时间(仅用Irvine32原生函数)
INCLUDE Irvine32.inc

.data
; 循环配置(仅用32位常量,避免LARGE_INTEGER)
OUTER_LOOP_COUNT = 3
INNER_LOOP_COUNT = 1000000
innerCountVal DWORD INNER_LOOP_COUNT ; 内存变量存储内循环次数(解决mul立即数问题)
startTime DWORD ?                     ; 毫秒级时间戳(Irvine32原生支持)

; 提示信息
msg1      BYTE "Please wait...", 0dh, 0ah, 0
msg2      BYTE "Elapsed milliseconds (Mul指令):", 0
msg3      BYTE "Elapsed milliseconds (Add指令):", 0
msg4      BYTE "Outer loop count: ", 0
msg5      BYTE "Inner loop count per outer: ", 0
msg6      BYTE "Total iterations: ", 0
msgEnd    BYTE 0dh,0ah,"按任意键退出...",0

.code
main PROC
    ; 初始化控制台
    call Clrscr
    mov edx, OFFSET msg1
    call WriteString

    ; ========== 测试Mul指令耗时(毫秒级,Irvine32原生) ==========
    call GetMSeconds
    mov startTime, eax
    mov ecx, OUTER_LOOP_COUNT
L1:
    call innerLoop_Mul
    loop L1
    call GetMSeconds
    sub eax, startTime

    ; 显示Mul指令耗时
    mov edx, OFFSET msg2
    call WriteString
    call WriteDec
    call Crlf

    ; ========== 测试Add指令耗时(对比) ==========
    call GetMSeconds
    mov startTime, eax
    mov ecx, OUTER_LOOP_COUNT
L2:
    call innerLoop_Add
    loop L2
    call GetMSeconds
    sub eax, startTime

    ; 显示Add指令耗时
    mov edx, OFFSET msg3
    call WriteString
    call WriteDec
    call Crlf

    ; ========== 显示循环配置信息(修正mul立即数错误) ==========
    call Crlf
    mov edx, OFFSET msg4
    call WriteString
    mov eax, OUTER_LOOP_COUNT
    call WriteDec
    call Crlf

    mov edx, OFFSET msg5
    call WriteString
    mov eax, INNER_LOOP_COUNT
    call WriteDec
    call Crlf

    mov edx, OFFSET msg6
    call WriteString
    mov eax, OUTER_LOOP_COUNT
    mul innerCountVal ; mul + 内存变量(合法),计算总迭代次数
    call WriteDec
    call Crlf

    ; 暂停退出
    mov edx, OFFSET msgEnd
    call WriteString
    call WaitMsg

    exit
main ENDP

; Mul指令内循环(核心耗时逻辑)
innerLoop_Mul PROC
    push ecx
    mov ecx, innerCountVal ; 用内存变量,避免立即数
L1:
    mul eax ; 消耗CPU周期(mul eax 是合法指令,隐含操作数EAX)
    loop L1
    pop ecx
    ret
innerLoop_Mul ENDP

; Add指令内循环(对比性能)
innerLoop_Add PROC
    push ecx
    mov ecx, innerCountVal
L1:
    add eax, eax ; 替换mul为add,测试不同指令耗时
    loop L1
    pop ecx
    ret
innerLoop_Add ENDP
END main
=============================
Please wait...
Elapsed milliseconds (Mul指令):5
Elapsed milliseconds (Add指令):7

Outer loop count: 3
Inner loop count per outer: 1000000
Total iterations: 3000000

                                                             
复制代码
                                                         总结

基础调试主要解决 "循环次数过大、无反馈、控制台一闪而过";扩展方向包括:

• 精度提升:微秒级计时替代毫秒级;

• 交互增强:用户自定义循环次数;

• 性能分析:对比不同指令 / 循环方式的耗时;

• 上下文补充:显示 CPU 信息,让耗时分析更有意义。

bash 复制代码
; 在64模式下调用子程序(Callproc 64.asm)
;第5章示例
 
ExitProcess PROTO
WriteInt64 PROTO								;Irvine64链接库
Crlf PROTO										;Irvine64链接库
 
.code
main PROC
		sub rsp, 8								;对准堆栈指针
		sub rsp, 20h							;为影子参数保留32个字节
 
		mov rcx, 1								;依序传递参数
		mov rdx, 2	
		mov r8, 3
		mov r9, 4
		call AddFour							;在RAX中查找返回值
		call WriteInt64							;显示数字
		call Crlf								;输出回车换行符
 
		mov ecx, 0
		call ExitProcess
main ENDP
 
AddFour PROC
		mov rax, rcx
		add rax, rdx
		add rax, r8
		add rax, r9									;
		ret
AddFour ENDP
 
END
bash 复制代码
从64 位调用约定、堆栈操作、影子空间三个核心维度,解析这段程序的执行逻辑:
一、核心背景:x86-64 的微软调用约定(__fastcall)
这段代码遵循Windows x86-64 调用约定,核心规则:
参数传递:前 4 个参数依次用rcx/rdx/r8/r9传递,超过 4 个的参数压入堆栈;
影子空间:调用者必须在堆栈上预留32 字节(4 个 8 字节)的 "影子空间",供被调用者临时存储寄存器参数;
堆栈对齐:调用call前,rsp必须对齐到16 字节边界;
返回值:函数返回值存在rax中。
二、对代码执行流程的理解(结合堆栈变化)
以main函数的执行步骤为线索,对应堆栈变化:
1. 程序启动时的堆栈初始状态
OS 调用main前,rsp = 01AFE50(16 字节对齐);
执行call main(OS 发起的调用),rsp -= 8(压入返回地址到 OS),因此执行第 10 行前,rsp = 01AFE48。
2. 第 10 行:sub rsp, 8(堆栈对齐)
执行前rsp = 01AFE48,执行后rsp = 01AFE40;
作用:让rsp重新对齐到16 字节边界(01AFE40是 16 的倍数),满足 x86-64 调用约定的要求。
3. 第 11 行:sub rsp, 20h(预留影子空间)
执行后rsp = 01AFE20;
作用:在堆栈上预留32 字节(0x20)的影子空间,地址范围是01AFE20 ~ 01AFE3F(对应 4 个 8 字节空间),供AddFour过程使用。
4. 参数传递与调用AddFour
用rcx=1、rdx=2、r8=3、r9=4传递前 4 个参数;
执行call AddFour:rsp -= 8(压入返回地址到main),此时 **rsp = 01AFE18**(这是AddFour过程内的rsp值)。
5. AddFour过程的执行
逻辑:将rcx/rdx/r8/r9的值累加,结果存入rax;
注意:AddFour过程没有使用影子空间(但调用者main必须预留,这是调用约定的强制要求);
执行ret:rsp += 8(弹出返回地址),回到main后 **rsp = 01AFE20**(与调用前的rsp一致)。
6. 输出结果与程序退出
WriteInt64输出rax中的累加结果(1+2+3+4=10);
用ExitProcess退出程序(而非ret):因为main是 OS 启动的入口,直接ret可能导致堆栈未正确恢复,而ExitProcess是标准的程序退出方式。
三、对 "影子空间" 和堆栈操作的补充理解
影子空间的作用:被调用者可以将rcx/rdx/r8/r9中的参数 "备份" 到影子空间(比如过程内需要修改这些寄存器时),这段代码的AddFour没有用到,但调用者必须预留;
堆栈恢复的重要性:调用过程前后,rsp必须恢复到调用前的值(比如call AddFour前后rsp都是01AFE20),否则会导致堆栈失衡,程序崩溃;
16 字节对齐的必要性:x86-64 的 SSE 指令要求数据在 16 字节对齐的地址上,因此调用call前必须保证rsp是 16 的倍数。
四、程序的执行结果
这段代码的最终输出是10(1+2+3+4的结果),控制台会显示数字10并换行。
总结:这段代码是 x86-64 调用约定的典型示例,核心体现了 "寄存器传参、影子空间预留、堆栈对齐" 三个关键规则,是理解 64 位汇编子程序调用的基础案例。
bash 复制代码
要理解 x86-64 中SSE 指令的 16 字节对齐要求与call 前 rsp 必须 16 字节对齐的关联,需结合 Intel 官方手册的调用约定和硬件约束,以下是详细讲解与可运行的汇编示例:
一、核心背景:Intel 手册对 SSE 与堆栈对齐的要求
根据《Intel® 64 and IA-32 Architectures Software Developer Manual》(卷 1,第 10.2.4 节):
SSE 指令的内存操作数对齐要求:
SSE 指令(如movdqa、xmmword ptr)操作 128 位(16 字节)数据时,要求内存地址必须是 16 字节的整数倍;若地址未对齐,会触发#GP(0)(通用保护)异常。
x86-64 调用约定的堆栈对齐规则:
调用call指令前,堆栈指针rsp必须对齐到 16 字节边界(即rsp mod 16 = 0)。这是因为被调用函数可能使用 SSE 指令操作堆栈上的数据(如影子空间),未对齐会导致 SSE 指令异常。
二、为什么call前rsp必须 16 字节对齐?
call指令会将返回地址(8 字节)压入堆栈,导致rsp -= 8;
因此,调用call前,rsp必须是16n(16 的倍数),执行call后rsp变为16n - 8(仍满足 SSE 指令的 "16 字节对齐" 吗?不 ------ 但被调用函数会通过sub rsp, 8等操作重新对齐)。
本质:保证被调用函数内的堆栈操作(尤其是 SSE 指令) 能访问到 16 字节对齐的地址。
三、示例:SSE 指令操作堆栈数据(需 16 字节对齐)
以下是可运行的 x86-64 汇编代码(基于 Irvine64),演示 "SSE 指令操作堆栈上的 16 字节数据",并验证rsp对齐的必要性:
asm
INCLUDE Irvine64.inc

.data
    ; 16字节的测试数据(SSE操作的最小单位)
    sseData  XMMWORD 0123456789ABCDEFh, 0FEDCBA9876543210h

.code
main PROC
    ; ========== 1. 初始化堆栈对齐(call前rsp必须16字节对齐) ==========
    sub rsp, 8          ; 对齐rsp到16字节边界(假设程序启动时rsp=16n+8)
    sub rsp, 20h        ; 预留32字节影子空间(同时保证rsp仍为16的倍数)

    ; ========== 2. 传递参数,调用含SSE指令的函数 ==========
    mov rcx, OFFSET sseData  ; 参数1:SSE数据的地址
    call SSE_Stack_Op        ; 调用函数(call前rsp是16的倍数)

    ; ========== 3. 退出程序 ==========
    mov ecx, 0
    call ExitProcess
main ENDP

; ------------------------------------------------------------
; 函数:SSE_Stack_Op
; 功能:使用SSE指令操作堆栈上的16字节数据(需16字节对齐)
; 参数:rcx = 16字节数据的地址
; ------------------------------------------------------------
SSE_Stack_Op PROC
    ; 被调用函数内,先保证rsp对齐到16字节(可选,因call前已对齐)
    ; sub rsp, 8          ; 若需要,可补充对齐

    ; ========== 步骤1:将数据从内存加载到XMM寄存器(SSE指令) ==========
    movdqa xmm0, XMMWORD PTR [rcx]  ; 加载16字节数据到xmm0(要求rcx是16的倍数)

    ; ========== 步骤2:将XMM寄存器中的数据存储到堆栈的影子空间(需16字节对齐) ==========
    movdqa XMMWORD PTR [rsp+8], xmm0  ; 存储到堆栈(rsp+8必须是16的倍数)

    ; ========== 步骤3:验证数据(输出到控制台) ==========
    movdqa xmm1, XMMWORD PTR [rsp+8]  ; 从堆栈加载数据到xmm1
    call WriteXMMReg                  ; 输出xmm1的值(Irvine64函数)
    call Crlf

    ; ========== 恢复堆栈,返回 ==========
    ret
SSE_Stack_Op ENDP
END main
四、示例说明
堆栈对齐的关键步骤:
main中sub rsp, 8:将rsp对齐到 16 字节边界;
sub rsp, 20h:预留 32 字节影子空间,此时rsp仍为 16 的倍数(20h是 32,32 是 16 的倍数);
执行call SSE_Stack_Op前,rsp满足rsp mod 16 = 0。
SSE 指令的对齐要求:
movdqa xmm0, XMMWORD PTR [rcx]:要求rcx(sseData的地址)是 16 的倍数(汇编器会自动将XMMWORD变量对齐到 16 字节);
movdqa XMMWORD PTR [rsp+8], xmm0:rsp+8是 16 的倍数(因rsp是 16 的倍数,加 8 后是16n+8?不 ------ 实际rsp在call后是16n-8,rsp+8=16n,正好是 16 的倍数)。
若未对齐的后果:若call前rsp不是 16 的倍数,执行movdqa时会触发#GP(0)异常,程序崩溃。
五、Intel 手册的补充说明
对于不需要 SSE 指令的函数,rsp对齐仍需遵守(调用约定的强制要求);
若函数明确不使用 SSE 指令,可通过编译器选项(如/arch:IA32)放松对齐,但 x86-64 下不建议。
bash 复制代码
; 64位汇编 - SSE 16字节对齐演示 - 最终版
option casemap:none

EXTERN ExitProcess:PROC
EXTERN GetStdHandle:PROC
EXTERN WriteConsoleA:PROC

.data
align 16
; 小端序存储:低位字节在前
testData QWORD 0123456789ABCDEFh, 0FEDCBA9876543210h
result1  QWORD 2 DUP(0)
result2  QWORD 2 DUP(0)
hexBuffer BYTE 50 DUP(0)
bytesWritten DWORD 0
hexDigits BYTE "0123456789ABCDEF"
newline  BYTE 0Dh, 0Ah, 0

; 消息文本
msgTitle BYTE "=== SSE 16-BYTE ALIGNMENT DEMO ===", 0Dh, 0Ah
         BYTE "Note: Data displayed in correct byte order", 0Dh, 0Ah, 0
msg1     BYTE "1. Original Data (hex):           ", 0
msg2     BYTE "2. SSE Load (movdqa) Result:      ", 0
msg3     BYTE "3. SSE Add (paddq) Result:        ", 0
msg4     BYTE "4. SSE Shift (psllq) Result:      ", 0
msgNote  BYTE 0Dh, 0Ah, "Note: All SSE operations use 16-byte aligned access", 0Dh, 0Ah, 0

.code
main PROC
    sub rsp, 28h
    
    ; 显示标题
    lea rcx, msgTitle
    call PrintString
    
    ; 1. 显示原始数据(修正字节序)
    lea rcx, msg1
    call PrintString
    lea rcx, testData
    mov r8, 16
    call DisplayHexBigEndian  ; 使用大端序显示
    call PrintNewline
    
    ; 2. 测试SSE对齐加载 (movdqa)
    lea rcx, msg2
    call PrintString
    
    ; 对齐加载
    lea rax, testData
    movdqa xmm0, xmmword ptr [rax]
    
    ; 对齐存储
    lea rax, result1
    movdqa xmmword ptr [rax], xmm0
    
    ; 显示结果
    lea rcx, result1
    mov r8, 16
    call DisplayHexBigEndian
    call PrintNewline
    
    ; 3. 测试SSE打包四字加法 (paddq)
    lea rcx, msg3
    call PrintString
    
    ; 重新加载数据
    lea rax, testData
    movdqa xmm0, xmmword ptr [rax]
    
    ; SSE加法:每个64位元素分别相加
    paddq xmm0, xmm0      ; xmm0 = xmm0 + xmm0
    
    ; 存储结果
    lea rax, result2
    movdqa xmmword ptr [rax], xmm0
    
    ; 显示结果
    lea rcx, result2
    mov r8, 16
    call DisplayHexBigEndian
    call PrintNewline
    
    ; 4. 测试SSE四字左移 (psllq)
    lea rcx, msg4
    call PrintString
    
    ; 重新加载数据到XMM1
    lea rax, testData
    movdqa xmm1, xmmword ptr [rax]
    
    ; SSE左移:每个64位元素左移1位
    psllq xmm1, 1         ; xmm1 = xmm1 << 1
    
    ; 存储结果
    lea rax, result1
    movdqa xmmword ptr [rax], xmm1
    
    ; 显示结果
    lea rcx, result1
    mov r8, 16
    call DisplayHexBigEndian
    call PrintNewline
    
    ; 显示说明
    lea rcx, msgNote
    call PrintString
    
    ; 额外测试:非对齐访问对比
    call TestUnalignedAccess
    
    ; 退出
    xor rcx, rcx
    call ExitProcess
    add rsp, 28h
    ret
main ENDP

; 以大端序(人类可读)格式显示十六进制数据
DisplayHexBigEndian PROC
    push rbx
    push rsi
    push rdi
    sub rsp, 40h
    
    mov rsi, rcx        ; 数据地址
    mov rdi, r8         ; 字节数
    
    ; 获取控制台句柄
    mov rcx, -11
    call GetStdHandle
    mov rbx, rax
    
    ; 转换为大端序十六进制
    lea rcx, hexBuffer
    mov rdx, rsi
    mov r8, rdi
    call BytesToHexBigEndian
    
    ; 显示结果
    mov rcx, rbx
    lea rdx, hexBuffer
    mov r8, rax         ; 长度
    lea r9, bytesWritten
    mov qword ptr [rsp + 20h], 0
    call WriteConsoleA
    
    add rsp, 40h
    pop rdi
    pop rsi
    pop rbx
    ret
DisplayHexBigEndian ENDP

; 将二进制数据转换为大端序十六进制字符串
; rcx = 目标缓冲区, rdx = 源数据, r8 = 字节数
BytesToHexBigEndian PROC
    push rsi
    push rdi
    push rbx
    
    mov rsi, rdx        ; 源数据
    mov rdi, rcx        ; 目标缓冲区
    mov rcx, r8         ; 字节数
    
    ; 如果是16字节,先显示第一个QWORD
    cmp rcx, 16
    jne SimpleConvert
    
    ; 处理第一个QWORD (bytes 8-15)
    add rsi, 8          ; 从第二个QWORD开始(内存中的高位)
    mov rcx, 8
    
HighQword:
    movzx rax, byte ptr [rsi + rcx - 1]  ; 反向读取
    
    mov rbx, rax
    shr rbx, 4
    mov dl, [hexDigits + rbx]
    mov [rdi], dl
    inc rdi
    
    and rax, 0Fh
    mov dl, [hexDigits + rax]
    mov [rdi], dl
    inc rdi
    
    loop HighQword
    
    ; 添加空格
    mov byte ptr [rdi], ' '
    inc rdi
    
    ; 处理第二个QWORD (bytes 0-7)
    sub rsi, 8          ; 回到第一个QWORD
    mov rcx, 8
    
LowQword:
    movzx rax, byte ptr [rsi + rcx - 1]  ; 反向读取
    
    mov rbx, rax
    shr rbx, 4
    mov dl, [hexDigits + rbx]
    mov [rdi], dl
    inc rdi
    
    and rax, 0Fh
    mov dl, [hexDigits + rax]
    mov [rdi], dl
    inc rdi
    
    loop LowQword
    
    mov byte ptr [rdi], 0
    mov rax, 33         ; 32字符 + 1空格
    jmp Done
    
SimpleConvert:
    ; 简单转换(非16字节情况)
    test rcx, rcx
    jz Done
    
    add rsi, rcx
    dec rsi             ; 指向最后一个字节
    
ReverseLoop:
    movzx rax, byte ptr [rsi]
    
    mov rbx, rax
    shr rbx, 4
    mov dl, [hexDigits + rbx]
    mov [rdi], dl
    inc rdi
    
    and rax, 0Fh
    mov dl, [hexDigits + rax]
    mov [rdi], dl
    inc rdi
    
    dec rsi
    dec rcx
    jnz ReverseLoop
    
    mov byte ptr [rdi], 0
    mov rax, r8
    shl rax, 1          ; 每个字节2个字符
    
Done:
    pop rbx
    pop rdi
    pop rsi
    ret
BytesToHexBigEndian ENDP

; 测试非对齐访问
TestUnalignedAccess PROC
    push rbx
    sub rsp, 30h
    
    ; 显示标题
    mov rcx, -11
    call GetStdHandle
    mov rbx, rax
    
    mov rcx, rbx
    lea rdx, msgUnaligned
    mov r8, sizeof msgUnaligned - 1
    lea r9, bytesWritten
    mov qword ptr [rsp + 20h], 0
    call WriteConsoleA
    
    ; 测试movdqu(非对齐访问)
    lea rax, testData
    movdqu xmm2, xmmword ptr [rax]
    lea rax, result1
    movdqu xmmword ptr [rax], xmm2
    
    ; 显示结果应该相同
    mov rcx, rbx
    lea rdx, msgMovdqu
    mov r8, sizeof msgMovdqu - 1
    lea r9, bytesWritten
    mov qword ptr [rsp + 20h], 0
    call WriteConsoleA
    
    add rsp, 30h
    pop rbx
    ret
TestUnalignedAccess ENDP

; 打印字符串
PrintString PROC
    push rbx
    push rsi
    sub rsp, 20h
    
    mov rsi, rcx
    
    ; 计算长度
    xor rcx, rcx
CountLoop:
    cmp byte ptr [rsi + rcx], 0
    je FoundEnd
    inc rcx
    jmp CountLoop
FoundEnd:
    
    ; 获取句柄
    push rcx
    mov rcx, -11
    call GetStdHandle
    mov rbx, rax
    pop rcx
    
    ; 打印
    mov rdx, rsi
    mov r8, rcx
    lea r9, bytesWritten
    mov qword ptr [rsp + 20h], 0
    mov rcx, rbx
    call WriteConsoleA
    
    add rsp, 20h
    pop rsi
    pop rbx
    ret
PrintString ENDP

; 打印换行
PrintNewline PROC
    push rbx
    sub rsp, 20h
    
    mov rcx, -11
    call GetStdHandle
    mov rbx, rax
    
    mov rcx, rbx
    lea rdx, newline
    mov r8, 2
    lea r9, bytesWritten
    mov qword ptr [rsp + 20h], 0
    call WriteConsoleA
    
    add rsp, 20h
    pop rbx
    ret
PrintNewline ENDP

.data
msgUnaligned BYTE 0Dh, 0Ah, "5. Unaligned Access Test (movdqu):", 0Dh, 0Ah, 0
msgMovdqu    BYTE "   Result should be identical to movdqa", 0Dh, 0Ah, 0

END
=========================
=== SSE 16-BYTE ALIGNMENT DEMO ===
Note: Data displayed in correct byte order
1. Original Data (hex):           FEDCBA9876543210 0123456789ABCDEF
2. SSE Load (movdqa) Result:      FEDCBA9876543210 0123456789ABCDEF
3. SSE Add (paddq) Result:        FDB97530ECA86420 02468ACF13579BDE
4. SSE Shift (psllq) Result:      FDB97530ECA86420 02468ACF13579BDE

Note: All SSE operations use 16-byte aligned access

5. Unaligned Access Test (movdqu):
   Result should be identical to movdqa

一、核心背景:SSE 16 字节对齐的硬件要求

Intel SSE(Streaming SIMD Extensions)指令集是针对多媒体 / 数据并行优化的 SIMD 扩展,其对齐指令(如movdqa)强制要求内存操作数必须 16 字节对齐,而非对齐指令(如movdqu)无此限制。

Intel 官方手册(《Intel 64 and IA-32 Architectures Software Developer's Manual》卷 2)明确:

• movdqa(Move Aligned Quadword):加载 / 存储 128 位(16 字节)数据,内存地址必须是 16 的整数倍,否则触发 #GP(通用保护)异常;

• movdqu(Move Unaligned Quadword):功能同movdqa,但允许非对齐地址,代价是轻微性能损耗(硬件需拆分 / 合并数据);

• 大部分 SSE 算术指令(如paddq/psllq)仅操作 XMM 寄存器,无对齐要求,但数据加载阶段的对齐仍需关注。

二、代码逐模块解析(结合 Intel 手册)

  1. 数据段:16 字节对齐的关键

    asm

    .data

    align 16 ; 强制后续数据16字节对齐(Intel手册要求movdqa的内存操作数必须满足)

    testData QWORD 0123456789ABCDEFh, 0FEDCBA9876543210h ; 128位数据(2个QWORD)

    • align 16:汇编器指令,确保testData的起始地址是 16 的倍数(如0x0000000000404000),满足movdqa的硬件要求;

    • 小端序存储:Intel x86 架构是小端序,因此testData在内存中实际存储为:

    plaintext

    地址: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

    数据: EF CD AB 89 67 45 23 01 10 32 54 76 98 BA DC FE

    (低字节EF存低地址,高字节FE存高地址)。

  2. 主函数:SSE 核心操作演示

    (1) movdqa 对齐加载 / 存储

    asm

    lea rax, testData

    movdqa xmm0, xmmword ptr [rax] ; 对齐加载(Intel手册:地址必须16对齐)

    lea rax, result1

    movdqa xmmword ptr [rax], xmm0 ; 对齐存储

    • 符合 Intel 手册要求:testData和result1均通过align 16保证 16 字节对齐,因此movdqa执行无异常;

    • 功能:将testData的 128 位数据完整加载到 XMM0 寄存器,再存储到result1,数据无变化(演示 2 的输出与原始数据一致)。

    (2) paddq 打包四字加法(Intel 手册卷 2:3-417)

    asm

    movdqa xmm0, xmmword ptr [rax]

    paddq xmm0, xmm0 ; 每个64位元素独立加法(Packed Add Quadword)

    • 指令语义:将 XMM0 中的两个 64 位 QWORD 元素分别与自身相加(a=a+a);

    • 计算过程(大端序视角):

    o 高 64 位:FEDCBA9876543210h + FEDCBA9876543210h = FDB97530ECA86420h;

    o 低 64 位:0123456789ABCDEFh + 0123456789ABCDEFh = 02468ACF13579BDEh;

    • 无对齐要求:paddq仅操作 XMM 寄存器,Intel 手册未要求内存对齐,只需寄存器数据有效。

    (3) psllq 打包四字左移(Intel 手册卷 2:3-548)

    asm

    movdqa xmm1, xmmword ptr [rax]

    psllq xmm1, 1 ; 每个64位元素左移1位(Packed Shift Left Logical Quadword)

    • 指令语义:将 XMM1 中的两个 64 位 QWORD 元素逻辑左移 1 位(低位补 0);

    • 计算过程(大端序视角):

    o 高 64 位:FEDCBA9876543210h << 1 = FDB97530ECA86420h;

    o 低 64 位:0123456789ABCDEFh << 1 = 02468ACF13579BDEh;

    • 与paddq结果一致:左移 1 位等价于乘以 2,因此输出相同(演示 4)。

    (4) movdqu 非对齐访问测试

    asm

    movdqu xmm2, xmmword ptr [rax] ; 非对齐加载(Intel手册:允许任意地址)

    movdqu xmmword ptr [rax], xmm2

    • Intel 手册说明:movdqu是movdqa的非对齐版本,即使地址未 16 对齐也能执行;

    • 本示例中testData实际是对齐的,因此movdqu结果与movdqa完全一致(演示 5);若刻意将地址偏移 1 字节(如[rax+1]),movdqa会触发异常,而movdqu仍正常执行。

  3. 辅助函数:大端序显示(适配人类阅读习惯)

    Intel x86 是小端序,但人类习惯大端序(高位在前),因此BytesToHexBigEndian函数做了字节反转:

    asm

    movzx rax, byte ptr [rsi + rcx - 1] ; 反向读取(从高地址到低地址)

    • 原始内存小端序:EF CD AB 89 67 45 23 01 10 32 54 76 98 BA DC FE;

    • 转换为大端序显示:FEDCBA9876543210 0123456789ABCDEF(与代码中定义的初始值一致)。

  4. 对齐必要性:movdqa是 SSE 高性能操作的基础,Intel 硬件强制要求 16 字节对齐,需通过align 16汇编指令或编译器对齐属性保证;

  5. 非对齐兼容:movdqu是容错方案,适合无法保证对齐的场景(如动态内存),但需牺牲少量性能;

  6. 小端序适配:x86 内存小端序存储,显示时需转换为大端序才能匹配人类阅读习惯(如代码中DisplayHexBigEndian);

  7. SIMD 并行性:paddq/psllq等指令同时操作两个 64 位元素,体现 SSE 的 SIMD 核心优势(单指令多数据)。

相关推荐
资料,小偿17 小时前
基于8086计算器8086,8086的计算器,8086简易计算器矩阵键盘proteus8.9版本(4.10.2)
汇编·proteus
梓仁沐白1 天前
汇编语言与接口设计:微型计算机硬件系统
汇编
apcipot_rain2 天前
汇编语言与逆向分析 一轮复习笔记
汇编·笔记·逆向
切糕师学AI2 天前
ARM 汇编指令:MOV
汇编·arm开发
切糕师学AI3 天前
ARM 汇编指令:ORRS
汇编·arm开发
缘友一世3 天前
计算系统安全速成之链接:理解程序的构建过程【7】
汇编·计算机系统
3824278273 天前
汇编:宏汇编、宏库
汇编
3824278273 天前
汇编:条件汇编、
前端·汇编·数据库
white-persist3 天前
【攻防世界】reverse | simple-check-100 详细题解 WP
c语言·开发语言·汇编·数据结构·c++·python·算法