Windows64的nasm汇编详细教程,不是DOS!

这篇文章没有参考任何官方文档

是作者反汇编整理出来的

编译方法:

nasm  -f win64 -o 1.obj 1.asm
gcc 1.obj -o 1.exe
.\1

一、打印

        global main
        extern puts
SECTION .text
main:
        sub rsp, 20h
        mov rcx, message
        call puts
        add rsp, 20h
        ret
message:
        db "ASM!",0

等同于

        global main
        extern printf
section .text
main:
        sub rsp, 20h
        lea rcx, [message]
        call printf
        add rsp, 20h
        ret
message:
        db "ASM!",0

第一行:全局声明main主函数

第二行:声明外部函数,链接时用的

第三行:代码段开始,nasm特有

第四行:main函数

接下来:

为栈分配 32 字节空间(20h 十六进制表示 32)。这通常是为局部变量或函数调用准备栈空间。
将存储字符串 "ASM!" 的地址(即标签 `message` 的位置)放入寄存器 `rcx`,作为参数传递给函数。
调用外部函数 `puts`,该函数用于输出字符串到标准输出。

恢复栈指针,释放之前分配的 32 字节空间。
从函数返回。

 message部分

db "ASM!",0  ; 定义一个字节序列,包含字符串 "ASM!" 和字符串结束标志 0。

注意,printf是lea,puts是mov,功能差不多,但不能混用

二、简单的程序

 

global main
extern printf
extern scanf
extern system

SECTION .data
Format db "请输入用户名:",0
aS db "%s",0
byte_404012 db "请输入密码:",0
Buffer db "用户名和密码:",0
Command db "pause",0
string1 db "错误",0
string2 db "正确",0
Admin db "admin",0
Password db "123456",0

SECTION .bss
username resb 50
password resb 50

SECTION .text
main:
    push rbp
    mov rbp, rsp
    sub rsp, 190

    ; 输出提示scanf用户名
    lea rcx, [Format]
    call printf

    ; 读取用户名
    lea rdx, [username]
    lea rcx, [aS]
    call scanf

    ; 输出提示scanf密码
    lea rcx, [byte_404012]
    call printf

    ; 读取密码
    lea rdx, [password]
    lea rcx, [aS]
    call scanf

    ; cmp用户名
    lea rsi, [Admin]
    lea rdi, [username]
    call compare_strings
    cmp rax, 1
    jne error

    ; cmp密码
    lea rsi, [Password]
    lea rdi, [password]
    call compare_strings
    cmp rax, 1
    jne error

    lea rcx, [string2]
    call printf
    jmp end

compare_strings:
    push rbp
    mov rbp, rsp
    sub rsp, 16
    xor rax, rax
    xor rcx, rcx
loop_compare:
    mov dl, [rsi + rcx]
    mov al, [rdi + rcx]
    cmp dl, al
    jne not_l
    cmp dl, 0
    je l
    inc rcx
    jmp loop_compare

not_l:
    mov rax, 0
    leave
    ret

l:
    mov rax, 1
    leave
    ret

end:
    ; 暂停system
    lea rcx, [Command]
    call system

    mov eax, 0
    add rsp, 190
    pop rbp
    ret

error:
    lea rcx, [string1]
    call printf
    jmp end

以下是对这段 NASM 汇编代码的逐行解释:

global main
extern printf
extern scanf
extern system

  • 声明全局符号 main,表示这是程序的入口点。同时声明外部函数 printfscanf 和 system,表示程序将调用这些外部库函数。

SECTION.data
Format db "请输入用户名:",0
aS db "%s",0
byte_404012 db "请输入密码:",0
Buffer db "用户名和密码:",0
Command db "pause",0
string1 db "错误",0
string2 db "正确",0
Admin db "admin",0
Password db "123456",0

  • .data 段用于存储初始化的数据。
    • Format 存储字符串 "请输入用户名:" 和字符串结束符 0
    • aS 存储格式化字符串 "%s",用于读取字符串输入。
    • byte_404012 存储字符串 "请输入密码:" 和字符串结束符 0
    • Buffer 存储字符串 "用户名和密码:" 和字符串结束符 0
    • Command 存储字符串 "pause",用于暂停程序执行。
    • string1 存储字符串 "错误" 和字符串结束符 0
    • string2 存储字符串 "正确" 和字符串结束符 0
    • Admin 存储字符串 "admin" 和字符串结束符 0,作为管理员用户名。
    • Password 存储字符串 "123456" 和字符串结束符 0,作为管理员密码。

SECTION.bss
username resb 50
password resb 50

  • .bss 段用于存储未初始化的数据。
    • username 预留 50 个字节的空间,用于存储用户输入的用户名。
    • password 预留 50 个字节的空间,用于存储用户输入的密码。

SECTION.text
main:
    push rbp
    mov rbp, rsp
    sub rsp, 190

  • main 函数开始。
    • push rbp:将基址指针 rbp 压入栈,用于保存当前栈帧的基地址。
    • mov rbp, rsp:将栈指针 rsp 的值赋给 rbp,建立新的栈帧。
    • sub rsp, 190:为局部变量和临时数据在栈上分配 190 个字节的空间。

    ; 输出提示 scanf 用户名
    lea rcx, [Format]
    call printf

  • 输出提示用户输入用户名的字符串。
    • lea rcx, [Format]:将 Format 字符串的地址加载到寄存器 rcx 中,作为 printf 的参数。
    • call printf:调用 printf 函数输出字符串
    ; 读取用户名
    lea rdx, [username]
    lea rcx, [aS]
    call scanf

  • 读取用户输入的用户名。
    • lea rdx, [username]:将 username 的地址加载到寄存器 rdx 中,作为 scanf 的存储地址。
    • lea rcx, [aS]:将格式化字符串 "%s" 的地址加载到寄存器 rcx 中,作为 scanf 的格式参数。
    • call scanf:调用 scanf 函数读取用户输入的字符串,并存储到 username 中。

    ; 输出提示 scanf 密码
    lea rcx, [byte_404012]
    call printf

  • 输出提示用户输入密码的字符串。
    • lea rcx, [byte_404012]:将 byte_404012 字符串的地址加载到寄存器 rcx 中,作为 printf 的参数。
    • call printf:调用 printf 函数输出字符串。

    ; 读取密码
    lea rdx, [password]
    lea rcx, [aS]
    call scanf

  • 读取用户输入的密码。
    • lea rdx, [password]:将 password 的地址加载到寄存器 rdx 中,作为 scanf 的存储地址。
    • lea rcx, [aS]:将格式化字符串 "%s" 的地址加载到寄存器 rcx 中,作为 scanf 的格式参数。
    • call scanf:调用 scanf 函数读取用户输入的字符串,并存储到 password 中。

    ; cmp 用户名
    lea rsi, [Admin]
    lea rdi, [username]
    call compare_strings
    cmp rax, 1
    jne error

  • 比较用户输入的用户名和管理员用户名。
    • lea rsi, [Admin]:将管理员用户名 Admin 的地址加载到寄存器 rsi 中。
    • lea rdi, [username]:将用户输入的用户名 username 的地址加载到寄存器 rdi 中。
    • call compare_strings:调用 compare_strings 函数比较两个字符串。
    • cmp rax, 1:比较返回值 rax 是否为 1,表示两个字符串相等。
    • jne error:如果不相等,跳转到 error 标签处,表示用户名错误。

    ; cmp 密码
    lea rsi, [Password]
    lea rdi, [password]
    call compare_strings
    cmp rax, 1
    jne error

  • 比较用户输入的密码和管理员密码。
    • lea rsi, [Password]:将管理员密码 Password 的地址加载到寄存器 rsi 中。
    • lea rdi, [password]:将用户输入的密码 password 的地址加载到寄存器 rdi 中。
    • call compare_strings:调用 compare_strings 函数比较两个字符串。
    • cmp rax, 1:比较返回值 rax 是否为 1,表示两个字符串相等。
    • jne error:如果不相等,跳转到 error 标签处,表示密码错误。

lea rcx, [string2]
call printf
jmp end

  • 如果用户名和密码都正确,输出 "正确" 字符串。
    • lea rcx, [string2]:将字符串 "正确" 的地址加载到寄存器 rcx 中,作为 printf 的参数。
    • call printf:调用 printf 函数输出字符串。
    • jmp end:跳转到 end 标签处,结束程序。

compare_strings:
    push rbp
    mov rbp, rsp
    sub rsp, 16

  • compare_strings 函数开始。
    • push rbp:将基址指针 rbp 压入栈,用于保存当前栈帧的基地址。
    • mov rbp, rsp:将栈指针 rsp 的值赋给 rbp,建立新的栈帧。
    • sub rsp, 16:为局部变量和临时数据在栈上分配 16 个字节的空间。

    xor rax, rax
    xor rcx, rcx
loop_compare:
    mov dl, [rsi + rcx]
    mov al, [rdi + rcx]
    cmp dl, al
    jne not_l
    cmp dl, 0
    je l
    inc rcx
    jmp loop_compare

  • 比较两个字符串的循环部分。
    • xor rax, rax:将寄存器 rax 清零。
    • xor rcx, rcx:将寄存器 rcx 清零,用于作为字符串索引。
    • loop_compare:循环标签。
    • mov dl, [rsi + rcx]:将第一个字符串(由 rsi 指向)的当前字符加载到寄存器 dl 中。
    • mov al, [rdi + rcx]:将第二个字符串(由 rdi 指向)的当前字符加载到寄存器 al 中。
    • cmp dl, al:比较两个字符是否相等。
    • jne not_l:如果不相等,跳转到 not_l 标签处。
    • cmp dl, 0:比较当前字符是否为字符串结束符 0
    • je l:如果是字符串结束符,表示两个字符串相等,跳转到 l 标签处。
    • inc rcx:增加字符串索引 rcx
    • jmp loop_compare:跳转到循环开始处,继续比较下一个字符。

not_l:
    mov rax, 0
    leave
    ret

  • 如果两个字符串不相等,设置返回值为 0,并返回。
    • mov rax, 0:将返回值设置为 0。
    • leave:恢复栈帧,相当于 mov rsp, rbp; pop rbp
    • ret:从函数返回。

l:
    mov rax, 1
    leave
    ret

  • 如果两个字符串相等,设置返回值为 1,并返回。
    • mov rax, 1:将返回值设置为 1。
    • leave:恢复栈帧,相当于 mov rsp, rbp; pop rbp
    • ret:从函数返回。

end:
    ; 暂停 system
    lea rcx, [Command]
    call system

  • 暂停程序执行。
    • lea rcx, [Command]:将字符串 "pause" 的地址加载到寄存器 rcx 中,作为 system 的参数。
    • call system:调用 system 函数执行系统命令,这里是暂停程序。

    mov eax, 0
    add rsp, 190
    pop rbp
    ret

  • 结束程序。
    • mov eax, 0:将返回值设置为 0。
    • add rsp, 190:恢复栈指针,释放之前分配的空间。
    • pop rbp:恢复基址指针 rbp
    • ret:从函数返回。

error:
    lea rcx, [string1]
    call printf
    jmp end

  • 如果用户名或密码错误,输出 "错误" 字符串,并跳转到 end 标签处结束程序。
    • lea rcx, [string1]:将字符串 "错误" 的地址加载到寄存器 rcx 中,作为 printf 的参数。
    • call printf:调用 printf 函数输出字符串。
    • jmp end:跳转到 end 标签处,结束程序。

 三、文件操作

global main
extern fopen
extern fwrite
extern fclose
extern printf

SECTION .data
write_mode db "w", 0
file_name db "123.txt", 0
error_msg db "Error opening file", 0
hello_msg db "hello", 0
file_mode db "w",0
content db "Hello, this is a test content for file writing.",0
format db "File written successfully.",0
error db "Error opening file for writing."

SECTION .text
main:
    push rbp
    mov rbp, rsp
    sub rsp, 48 ; 对应 sub rsp, 30h

    ; 设置 "w" 模式
    mov rdx, write_mode
    ; 设置文件名 "123.txt"
    mov rcx, file_name
    call fopen

    ; 将返回的文件指针存储到相对于 rbp - 8 的位置
    mov qword [rbp - 8], rax

    cmp qword [rbp - 8], 0
    jnz file_open_success

    ; 文件打开失败
    mov rcx, error_msg
    call printf
    mov eax, 1
    jmp end_program

file_open_success:
    mov rax, [rbp - 8]
    mov r9, rax ; Stream
    mov r8d, 5 ; ElementCount
    mov edx, 1 ; ElementSize
    mov rcx, hello_msg ; "hello"
    call fwrite

    mov rax, [rbp - 8]
    mov rcx, rax ; Stream
    call fclose

    mov eax, 0

end_program:
    add rsp, 48 ; 对应 add rsp, 30h
    pop rbp
    ret

以下是对这段 NASM 汇编代码的逐行解析:

global main
extern fopen
extern fwrite
extern fclose
extern printf

  • 声明全局符号 main,表示这是程序的入口点。同时声明外部函数 fopenfwritefclose 和 printf,表示程序将调用这些外部库函数。

SECTION.data
write_mode db "w", 0
file_name db "123.txt", 0
error_msg db "Error opening file", 0
hello_msg db "hello", 0
file_mode db "w",0
content db "Hello, this is a test content for file writing.",0
format db "File written successfully.",0
error db "Error opening file for writing."

  • .data 段用于存储初始化的数据。
    • write_mode 存储字符串 "w" 和字符串结束符 0,表示以写入模式打开文件。
    • file_name 存储字符串 "123.txt" 和字符串结束符 0,表示要打开的文件名。
    • error_msg 存储字符串 "Error opening file" 和字符串结束符 0,用于在文件打开失败时输出错误信息。
    • hello_msg 存储字符串 "hello" 和字符串结束符 0,将写入文件的内容。
    • file_mode 存储字符串 "w" 和字符串结束符 0,与 write_mode 重复,可能是冗余的。
    • content 存储一段较长的字符串,作为另一个可能写入文件的内容。
    • format 存储字符串 "File written successfully." 和字符串结束符 0,用于在文件写入成功时输出提示信息。
    • error 存储字符串 "Error opening file for writing." 和字符串结束符 0,与 error_msg 类似,可能是冗余的。

SECTION.text
main:
    push rbp
    mov rbp, rsp
    sub rsp, 48 ; 对应 sub rsp, 30h

  • main 函数开始。
    • push rbp:将基址指针 rbp 压入栈,用于保存当前栈帧的基地址。
    • mov rbp, rsp:将栈指针 rsp 的值赋给 rbp,建立新的栈帧。
    • sub rsp, 48:为局部变量和临时数据在栈上分配 48 个字节的空间。

    ; 设置 "w" 模式
    mov rdx, write_mode
    ; 设置文件名 "123.txt"
    mov rcx, file_name
    call fopen

  • 尝试以写入模式打开文件 "123.txt"。
    • mov rdx, write_mode:将写入模式字符串的地址加载到寄存器 rdx 中,作为 fopen 的模式参数。
    • mov rcx, file_name:将文件名字符串的地址加载到寄存器 rcx 中,作为 fopen 的文件名参数。
    • call fopen:调用 fopen 函数打开文件,并返回文件指针。

    ; 将返回的文件指针存储到相对于 rbp - 8 的位置
    mov qword [rbp - 8], rax

  • 将 fopen 返回的文件指针存储在栈上相对于 rbp - 8 的位置,以便后续使用。

    cmp qword [rbp - 8], 0
    jnz file_open_success

  • 检查文件是否成功打开。如果文件指针不为零,表示文件打开成功,跳转到 file_open_success 标签处继续执行;如果文件指针为零,表示文件打开失败。

    ; 文件打开失败
    mov rcx, error_msg
    call printf
    mov eax, 1
    jmp end_program

  • 如果文件打开失败,输出错误信息,设置返回值为 1,并跳转到 end_program 标签处准备结束程序。
    • mov rcx, error_msg:将错误信息字符串的地址加载到寄存器 rcx 中,作为 printf 的参数。
    • call printf:调用 printf 函数输出错误信息。
    • mov eax, 1:将返回值设置为 1。
    • jmp end_program:跳转到 end_program 标签处。

file_open_success:
    mov rax, [rbp - 8]
    mov r9, rax ; Stream
    mov r8d, 5 ; ElementCount
    mov edx, 1 ; ElementSize
    mov rcx, hello_msg ; "hello"
    call fwrite

  • 如果文件打开成功,将文件指针加载到寄存器中,设置写入参数,然后调用 fwrite 函数向文件写入内容。
    • mov rax, [rbp - 8]:将存储在栈上的文件指针加载到寄存器 rax 中。
    • mov r9, rax:将文件指针复制到寄存器 r9,作为 fwrite 的第一个参数(文件流指针)。
    • mov r8d, 5:设置写入的元素个数为 5,这里可能是错误的,因为 hello_msg 的长度并不是 5。
    • mov edx, 1:设置每个元素的大小为 1 字节。
    • mov rcx, hello_msg:将要写入的内容(字符串 "hello")的地址加载到寄存器 rcx 中。
    • call fwrite:调用 fwrite 函数向文件写入内容。

    mov rax, [rbp - 8]
    mov rcx, rax ; Stream
    call fclose

  • 关闭文件。
    • mov rax, [rbp - 8]:将存储在栈上的文件指针加载到寄存器 rax 中。
    • mov rcx, rax:将文件指针复制到寄存器 rcx,作为 fclose 的参数(文件流指针)。
    • call fclose:调用 fclose 函数关闭文件。

asm

Copy

    mov eax, 0

  • 设置返回值为 0,表示程序正常结束。

end_program:
    add rsp, 48 ; 对应 add rsp, 30h
    pop rbp
    ret

  • end_program 标签处,恢复栈指针,恢复基址指针,并从函数返回。
    • add rsp, 48:恢复栈指针,释放之前分配的空间。
    • pop rbp:恢复基址指针 rbp
    • ret:从函数返回。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值