《汇编语言》(第四版)- 王爽著。
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-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(十进制)。 - 核心工具: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 是结束符)。 - 主程序执行流程(main PROC部分)
这是程序的 "主线",按顺序执行:
步骤 1:算术运算:严格遵循 "先乘除后加减",用寄存器暂存结果(eax/ebx 是 32 位通用寄存器,相当于临时变量);
步骤 2:数值转字符串:调用int_to_str,把 EBX 中的 90 转成 result_str 中的 "90";
步骤 3:拼接字符串:把提示语msg("计算... 为:")和结果result_str("90")合并成完整字符串;
步骤 4:显示消息框:调用MessageBoxA,弹出窗口显示完整内容;
步骤 5:退出程序:调用ExitProcess,程序正常结束。 - 函数调用规则(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
