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
,表示这是程序的入口点。同时声明外部函数printf
、scanf
和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
,表示这是程序的入口点。同时声明外部函数fopen
、fwrite
、fclose
和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
:从函数返回。