板凳————————————(枯藤 )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
相关推荐
我在人间贩卖青春5 天前
汇编之伪指令
汇编·伪指令
我在人间贩卖青春5 天前
汇编之伪操作
汇编·伪操作
济6175 天前
FreeRTOS基础--堆栈概念与汇编指令实战解析
汇编·嵌入式·freertos
myloveasuka5 天前
汇编TEST指令
汇编
我在人间贩卖青春5 天前
汇编编程驱动LED
汇编·点亮led
我在人间贩卖青春5 天前
汇编和C编程相互调用
汇编·混合编程
myloveasuka6 天前
寻址方式笔记
汇编·笔记·计算机组成原理
请输入蚊子6 天前
《操作系统真象还原》 第六章 完善内核
linux·汇编·操作系统·bochs·操作系统真像还原
myloveasuka6 天前
指令格式举例
汇编·笔记·计算机组成原理
我在人间贩卖青春7 天前
汇编之分支跳转指令
汇编·arm·分支跳转