板凳————————————(枯藤 )vs2019+win10

《汇编语言》(第四版)- 王爽著。

https://blog.csdn.net/jx520/category_5829409.html

汇编语言:基于X86处理器

https://blog.csdn.net/chenyijun/category_6432537_2.html

bash 复制代码
在这里插入代码片


.386
.model flat, stdcall
option casemap:none

; 必须链接的库
includelib kernel32.lib
includelib user32.lib

; 函数声明
ExitProcess PROTO STDCALL :DWORD
MessageBoxA PROTO STDCALL :DWORD,:DWORD,:DWORD,:DWORD

.data
    sum DWORD 0
    ; 简单字符串 - 避免换行问题
    greeting1 BYTE "Welcome to the Encryption Demo", 0
    greeting2 BYTE "Created by Kip Irvine", 0
    caption BYTE "Greeting", 0

.code
main PROC
    ; 计算 5+6
    mov eax, 5
    add eax, 6
    mov sum, eax
    
    ; 显示消息框
    push 0                    ; uType = MB_OK
    push OFFSET caption       ; lpCaption
    push OFFSET greeting1     ; lpText
    push 0                    ; hWnd = NULL
    call MessageBoxA
    
    ; 退出程序
    push 0
    call ExitProcess
main ENDP
END main

严重性 代码 说明 项目 文件 行 禁止显示状态

错误 LNK1104 无法打开文件"Debug\showgreeting_final.obj" asm32 C:\Users\lenovo\source\repos\asm32\LINK 1

可以在 VS 开发者命令提示符 中手动执行:

cmd

c 复制代码
**cd C:\Users\lenovo\source\repos\asm32
ml /c /coff showgreeting_final.asm
link /subsystem:console showgreeting_final.obj
showgreeting_final.exe**

这个LNK1104错误是 VS 项目没找到编译生成的.obj文件,核心是VS 的编译流程和手动命令行的文件命名不匹配,以下是 VS 内的解决方法:

方法 1:让 VS 自动生成正确的.obj文件

右键项目 → 添加 → 现有项,把你的showgreeting_final.asm添加到 VS 项目中(确保文件在项目列表里);

右键项目 → 属性 → 配置属性 → 常规,确认 "目标文件名" 是showgreeting_final(和你的.asm文件名一致);

点击「生成 → 重新生成解决方案」,VS 会自动编译showgreeting_final.asm为showgreeting_final.obj,再链接成可执行文件。

方法 2:直接用 VS 调用你的手动命令(可选)

如果想保留手动命令的流程,可在 VS 中配置 "自定义生成步骤":

右键showgreeting_final.asm → 属性 → 常规,"项类型" 选择「自定义生成工具」;

点击「应用」后,会出现「自定义生成工具 → 常规」;

"命令行" 输入:

plaintext

ml /c /coff "(FullPath)"link/subsystem:console"(FullPath)" link /subsystem:console "(FullPath)"link/subsystem:console"(InputName).obj"

"输出" 输入:(InputName).obj;(InputName).obj;(InputName).obj;(InputName).exe;

点击「确定」,重新生成项目,VS 会执行你的手动命令并生成文件。

关键原因

你手动命令行生成的是showgreeting_final.obj,但 VS 项目默认找的是和项目名一致的.obj(比如项目名是asm32,会找asm32.obj),导致找不到文件。按方法 1 把.asm文件添加到项目并统一文件名,就能让 VS 自动匹配编译 / 链接流程啦~

bash 复制代码
在这里插入代码片


; x64安全内存访问+消息框显示 最终版
option casemap:none

; 必须链接的库:kernel32.lib(退出)、user32.lib(消息框)
includelib kernel32.lib
includelib user32.lib

; 函数声明(x64调用约定,参数通过 RCX、RDX、R8、R9 传递)
ExitProcess PROTO
MessageBoxA PROTO :DWORD, :DWORD, :DWORD, :DWORD  ; hWnd, lpText, lpCaption, uType

.data
    buffer BYTE 16 DUP(0)  ; 16字节安全缓冲区
    ; 纯单行字符串(无语法错误)
    greeting1 BYTE "Welcome to the Encryption Demo program created by Kip Irvine.", 0dh, 0ah, "If you wish to modify this program, please send me a copy.", 0dh, 0ah, 0
    caption BYTE "x64 Encryption Demo", 0  ; 消息框标题(末尾0标记字符串结束)

.code
main PROC
    sub rsp, 28h          ; x64必选:分配28h字节影子空间(遵循调用约定)
    
    ; 方法1:.data段缓冲区安全读写(原有逻辑不变)
    mov rax, offset buffer
    mov rdx, 0
    mov byte ptr [rax + rdx], 11h        ; buffer[0] = 0x11
    mov byte ptr [rax + rdx + 1], 22h    ; buffer[1] = 0x22
    mov byte ptr [rax + rdx*2], 33h      ; buffer[0] = 0x33(覆盖)
    mov byte ptr [rax + rdx*4], 44h      ; buffer[0] = 0x44(覆盖)
    
    ; 方法2:栈上安全分配与读写(原有逻辑不变)
    sub rsp, 16           ; 分配16字节局部栈空间
    mov rbx, rsp          ; RBX指向栈空间(避免越界)
    mov rdx, 0
    mov byte ptr [rbx + rdx], 55h        ; 栈[0] = 0x55
    mov byte ptr [rbx + rdx*4], 66h      ; 栈[0] = 0x66(覆盖)
    mov rdx, 1
    mov byte ptr [rbx + rdx*4], 77h      ; 栈[4] = 0x77
    mov byte ptr [rbx + rdx*2], 88h      ; 栈[2] = 0x88
    ; 读取验证
    mov dl, byte ptr [rbx + 4]           ; DL = 0x77(验证成功)
    add rsp, 16          ; 恢复栈空间(栈平衡)
    
    ; 核心:调用MessageBoxA弹出消息框(x64参数传递规则)
    mov rcx, 0                          ; RCX = hWnd(0表示无父窗口)
    mov rdx, offset greeting1           ; RDX = lpText(字符串内容地址)
    mov r8, offset caption              ; R8 = lpCaption(消息框标题地址)
    mov r9, 0                          ; R9 = uType(0表示仅显示"确定"按钮)
    call MessageBoxA                    ; 调用消息框API
    
    ; 退出程序
    xor rcx, rcx                        ; RCX = 退出码(0表示正常退出)
    call ExitProcess
    add rsp, 28h                        ; 恢复影子空间
    ret
main ENDP

END
bash 复制代码
在这里插入代码片


.386
.model flat, stdcall
.stack 4096
option casemap:none

; 链接必要库
includelib kernel32.lib
includelib user32.lib

; 函数声明
ExitProcess PROTO :DWORD
MessageBoxA PROTO :DWORD, :DWORD, :DWORD, :DWORD

.data
    msg BYTE "x86 32位汇编项目运行成功!", 0
    caption BYTE "成功提示", 0

.code
main PROC
    ; 调用MessageBoxA(x86参数逆序压栈)
    push 0                    ; uType = MB_OK
    push offset caption       ; 标题
    push offset msg           ; 消息内容
    push 0                    ; hWnd = NULL
    call MessageBoxA

    ; 退出程序
    push 0
    call ExitProcess
main ENDP
END main
bash 复制代码
在这里插入代码
; 1. 架构与编译配置(基础设置,告诉汇编器怎么编译)
.386
.model flat, stdcall
.stack 4096
option casemap:none

; 2. 链接系统库(调用Windows API需要的"工具包")
includelib kernel32.lib
includelib user32.lib

; 3. 声明要调用的Windows函数(告诉汇编器函数的"格式")
ExitProcess PROTO :DWORD
MessageBoxA PROTO :DWORD, :DWORD, :DWORD, :DWORD

; 4. 数据段(存储字符串、常量等数据,相当于"仓库")
.data
    msg BYTE "计算 100 + 50 - 30 × 2 的结果为:", 0  ; 提示语(末尾0是字符串结束标志)
    result_str BYTE 10 DUP(0)  ; 存储转换后的数字字符串(10字节足够存32位整数)
    caption BYTE "算术运算结果", 0  ; 消息框标题
    ten DWORD 10  ; 数值转字符串用的除数(固定为10,十进制转换)

; 5. 辅助函数:纯汇编实现"整数转字符串"(核心工具函数)
.code
int_to_str PROC
    ; 保存寄存器状态(避免影响主程序的寄存器数据,汇编规范)
    push eax
    push ebx
    push ecx
    push edx
    push esi
    push edi

    mov ebx, ten  ; EBX = 10(除数,用于拆分数字的每一位)
    mov ecx, 0    ; ECX = 0(计数器,记录数字的位数)

    ; 特殊情况:如果要转换的数字是0,直接存'0'
    test eax, eax  ; 检测EAX(输入的整数)是否为0(Z标志位=1表示0)
    jnz convert_loop  ; 不为0则跳转到转换循环
    mov byte ptr [edi], '0'  ; 向缓冲区写入ASCII字符'0'
    inc edi  ; 缓冲区指针向后移动1位
    jmp end_convert  ; 直接跳转到转换结束

convert_loop:  ; 循环拆分数字的每一位(比如90→9和0)
    xor edx, edx  ; 清空EDX(除法要求EDX:EAX联合作为被除数,先清0高位)
    div ebx       ; 除法:EAX(被除数)÷ EBX(10)→ EAX=商,EDX=余数(0-9)
    add dl, '0'   ; 余数转ASCII字符(比如余数0→'0',9→'9',ASCII码差值是48,'0'的ASCII是48)
    push edx      ; 余数压栈保存(因为除法得到的是"个位→十位→百位",后续要逆序输出)
    inc ecx       ; 位数计数器+1(比如90是2位,ECX最终=2)
    test eax, eax  ; 检测商是否为0(商为0表示所有位都拆完了)
    jnz convert_loop  ; 商不为0则继续拆分

build_str:  ; 逆序弹出栈中余数,构造字符串(比如栈中是0→9,弹出后是9→0→"90")
    pop edx  ; 弹出栈顶的余数(先弹0,再弹9)
    mov byte ptr [edi], dl  ; 向缓冲区写入当前位的ASCII字符
    inc edi  ; 缓冲区指针向后移动1位
    loop build_str  ; ECX计数器-1,直到ECX=0(所有位都处理完)

end_convert:  ; 转换结束,添加字符串结束标志
    mov byte ptr [edi], 0  ; 向缓冲区末尾写入0(Windows API识别字符串结束的关键)

    ; 恢复寄存器状态(和开头的push对应,栈平衡)
    pop edi
    pop esi
    pop edx
    pop ecx
    pop ebx
    pop eax
    ret  ; 函数返回(回到调用它的地方)
int_to_str ENDP

; 6. 主程序(程序入口,核心逻辑执行处)
main PROC
    ; -------------------------- 第一步:执行算术运算(100 + 50 - 30 × 2)--------------------------
    ; 规则:先乘除后加减,用寄存器暂存中间结果
    mov eax, 30          ; EAX = 30(先算乘法:30×2,把30存入EAX)
    imul eax, 2          ; EAX = 30 × 2 = 60(imul是32位有符号乘法,结果存在EAX)
    mov ebx, 100         ; EBX = 100(再算加法:100+50,把100存入EBX)
    add ebx, 50          ; EBX = 100 + 50 = 150(add是加法,结果存在EBX)
    sub ebx, eax         ; EBX = 150 - 60 = 90(sub是减法,最终结果存在EBX)

    ; -------------------------- 第二步:把数值(90)转为字符串("90")--------------------------
    mov eax, ebx                ; EAX = 90(把最终结果从EBX传到EAX,符合int_to_str的输入要求)
    lea edi, result_str         ; EDI = result_str的地址(告诉int_to_str:字符串存到这个缓冲区)
    call int_to_str             ; 调用整数转字符串函数,执行后result_str = "90"

    ; -------------------------- 第三步:拼接提示语和结果("计算...为:" + "90" = 完整字符串)--------------------------
    mov ecx, offset msg  ; ECX = msg的起始地址(用于遍历提示语)
concat_loop:  ; 遍历提示语,找到它的结束位置(末尾的0)
    mov al, byte ptr [ecx]  ; AL = ECX指向的当前字符(从"计"开始,逐个取)
    test al, al  ; 检测当前字符是否为0(字符串结束标志)
    jz end_concat  ; 是0则停止遍历,跳转到拼接结果
    inc ecx  ; ECX指针向后移动1位(取下一个字符)
    jmp concat_loop

end_concat:  ; 开始拼接结果字符串(把"90"复制到提示语末尾)
    mov esi, offset result_str  ; ESI = result_str的起始地址("90"的地址)
copy_result:
    mov al, byte ptr [esi]  ; AL = ESI指向的当前字符(先取'9',再取'0')
    test al, al  ; 检测当前字符是否为0(result_str的结束标志)
    jz end_copy  ; 是0则停止复制
    mov byte ptr [ecx], al  ; 把当前字符写入提示语的末尾(ECX此时指向msg的0位置)
    inc ecx  ; msg的指针向后移动1位
    inc esi  ; result_str的指针向后移动1位
    jmp copy_result

end_copy:
    mov byte ptr [ecx], 0  ; 给完整字符串添加结束标志0(避免乱码)

    ; -------------------------- 第四步:弹出消息框显示结果 --------------------------
    ; x86汇编调用函数规则:参数逆序压栈(从最后一个参数开始压)
    push 0                    ; 第4个参数:uType = 0(消息框只有"确定"按钮)
    push offset caption       ; 第3个参数:lpCaption = 标题的地址
    push offset msg           ; 第2个参数:lpText = 完整提示语的地址(含结果)
    push 0                    ; 第1个参数:hWnd = 0(无父窗口,独立消息框)
    call MessageBoxA          ; 调用Windows的消息框API,弹出窗口

    ; -------------------------- 第五步:退出程序 --------------------------
    push 0  ; 参数:dwExitCode = 0(告诉系统程序正常退出)
    call ExitProcess  ; 调用Windows的退出API,结束程序

main ENDP  ; 主程序结束
END main  ; 整个汇编程序结束(指定main为入口)

核心逻辑拆解(按执行顺序)

  1. 基础设置(代码开头 1-4 部分)
    .386:指定用 x86 32 位架构(只能用 32 位寄存器,如 eax/ebx,不能用 64 位的 rax/rbx);
    .model flat, stdcall:平展内存模型(Windows 程序通用)+ stdcall 调用约定(函数调用后自动清理栈);
    .stack 4096:分配 4KB 栈空间(程序运行时临时存储数据,比如函数参数、寄存器值);
    includelib kernel32.lib/user32.lib:链接 Windows 系统库(kernel32 负责程序退出,user32 负责消息框显示);
    .data段:存储字符串和常量,比如msg是提示语,ten是转换数字用的 10(十进制)。
  2. 核心工具:int_to_str函数(数值转字符串)
    为什么需要这个函数?Windows 的MessageBoxA只能显示字符串,不能直接显示数字(比如 90 是数值,必须转成 ASCII 字符串 "90" 才能显示)。这个函数的作用就是 "把数值 90→字符串 "90"",拆解步骤:
    输入:EAX=90(要转换的数值),EDI=result_str 的地址(字符串存哪里);
    步骤 1:90 ÷ 10 → 商 = 9,余数 = 0 → 余数 0 转 ASCII='0',压栈;
    步骤 2:商 9 ÷ 10 → 商 = 0,余数 = 9 → 余数 9 转 ASCII='9',压栈;
    步骤 3:栈中是 ['0','9'],逆序弹出→'9'→'0',存入 result_str→"90";
    步骤 4:添加字符串结束标志 0,最终 result_str = "90\0"(\0 是结束符)。
  3. 主程序执行流程(main PROC部分)
    这是程序的 "主线",按顺序执行:
    步骤 1:算术运算:严格遵循 "先乘除后加减",用寄存器暂存结果(eax/ebx 是 32 位通用寄存器,相当于临时变量);
    步骤 2:数值转字符串:调用int_to_str,把 EBX 中的 90 转成 result_str 中的 "90";
    步骤 3:拼接字符串:把提示语msg("计算... 为:")和结果result_str("90")合并成完整字符串;
    步骤 4:显示消息框:调用MessageBoxA,弹出窗口显示完整内容;
    步骤 5:退出程序:调用ExitProcess,程序正常结束。
  4. 函数调用规则(x86 stdcall 约定)
    x86 汇编调用函数时,参数要 "逆序压栈"(从最后一个参数开始压),比如MessageBoxA的参数顺序是:hWnd → lpText → lpCaption → uType,压栈时要反过来:
    asm
    push 0(uType,最后一个参数)→ push offset caption(lpCaption)→ push offset msg(lpText)→ push 0(hWnd,第一个参数)→ call MessageBoxA
bash 复制代码
.386
.model flat, stdcall
.stack 4096
option casemap:none

; 仅链接Windows系统库(无外部依赖)
includelib kernel32.lib
includelib user32.lib

; 函数声明
ExitProcess PROTO :DWORD
MessageBoxA PROTO :DWORD, :DWORD, :DWORD, :DWORD

.data
    ; 自定义表达式提示语(可修改此处的表达式文本,与下方运算逻辑对应)
    msg BYTE "计算 25 × 4 - 18 ÷ 3 的结果为:", 0
    result_str BYTE 10 DUP(0)  ; 存储数字字符串
    caption BYTE "自定义算术运算结果", 0
    ten DWORD 10  ; 数值转字符串用除数

.code
; 辅助函数:32位整数转字符串(纯汇编实现)
; 输入:EAX = 待转换整数,EDI = 字符串缓冲区地址
; 输出:无(字符串存入缓冲区,带0结束符)
int_to_str PROC
    push eax
    push ebx
    push ecx
    push edx
    push esi
    push edi

    mov ebx, ten
    mov ecx, 0

    ; 处理0的特殊情况
    test eax, eax
    jnz convert_loop
    mov byte ptr [edi], '0'
    inc edi
    jmp end_convert

convert_loop:
    xor edx, edx
    div ebx       ; EAX=商,EDX=余数(0-9)
    add dl, '0'   ; 余数转ASCII
    push edx      ; 压栈保存(逆序弹出)
    inc ecx
    test eax, eax
    jnz convert_loop

build_str:
    pop edx
    mov byte ptr [edi], dl
    inc edi
    loop build_str

end_convert:
    mov byte ptr [edi], 0  ; 字符串结束符

    pop edi
    pop esi
    pop edx
    pop ecx
    pop ebx
    pop eax
    ret
int_to_str ENDP

main PROC
    ; 第一步:自定义算术运算(修改此处实现不同表达式,遵循"先乘除后加减")
    ; 表达式:25×4 - 18÷3 = 100 - 6 = 94
    mov eax, 25          ; 第一个乘法项:25
    imul eax, 4          ; EAX = 25×4 = 100
    mov ebx, eax         ; EBX = 100(暂存乘法结果)

    mov eax, 18          ; 第一个除法项:18
    xor edx, edx         ; 除法前清空EDX(无符号除法用div,有符号用idiv)
    mov ecx, 3           ; 除数3
    div ecx              ; EAX = 18÷3 = 6,EDX=余数0

    sub ebx, eax         ; EBX = 100 - 6 = 94(最终结果)

    ; 第二步:数值转字符串(EBX=结果→result_str)
    mov eax, ebx
    lea edi, result_str
    call int_to_str

    ; 第三步:拼接提示语和结果
    mov ecx, offset msg
concat_loop:
    mov al, byte ptr [ecx]
    test al, al
    jz end_concat
    inc ecx
    jmp concat_loop
end_concat:
    mov esi, offset result_str
copy_result:
    mov al, byte ptr [esi]
    test al, al
    jz end_copy
    mov byte ptr [ecx], al
    inc ecx
    inc esi
    jmp copy_result
end_copy:
    mov byte ptr [ecx], 0

    ; 第四步:弹出消息框显示结果
    push 0
    push offset caption
    push offset msg
    push 0
    call MessageBoxA

    ; 第五步:退出程序
    push 0
    call ExitProcess
main ENDP
END main
相关推荐
T.Ree.4 小时前
汇编_movsd和stosd
汇编
日更嵌入式的打工仔7 小时前
MCUXpresso开启汇编调试
汇编·单片机·nxp·mcuxpresso
切糕师学AI1 天前
ARM中的汇编指令:MRS和MSR
汇编·嵌入式开发
缘友一世2 天前
计算系统安全速成之汇编基础【1】
汇编
white-persist3 天前
【攻防世界】reverse | IgniteMe 详细题解 WP
c语言·汇编·数据结构·c++·python·算法·网络安全
小邓   ༽3 天前
50道C++编程练习题及解答-C编程例题
c语言·汇编·c++·编程练习·c语言练习题
资料,小偿5 天前
4.1.2基于51单片机汇编语言出租车计价器proteus仿真出租车计价器,汇编语言51单片机
汇编·51单片机·proteus
ol木子李lo7 天前
Visual studio 2022高亮汇编(ASM)语法方法
汇编·ide·windows·visual studio
资料,小偿8 天前
4.1.1基于51单片机汇编语言出租车计价器可切换白天黑夜,可修改价格
汇编·51单片机·proteus