提问人:Logan Seeley 提问时间:9/16/2023 最后编辑:Peter CordesLogan Seeley 更新时间:9/16/2023 访问量:113
x86_64 nasm read syscall 不是以 null 结尾的
x86_64 nasm read syscall isn't null-terminated
问:
x86_64 nasm read syscall 不是以 null 结尾的
刚开始组装。事实上,这是我的第一个hello world程序。 操作系统: Arch Linux (64-bit)
逻辑
我有三个功能。和。:
通过循环遍历字节直到找到 null 字节来查找字符串的长度。strlen
stdin
stdout
strlen
stdin
只需使用读取和写入系统调用即可。stdout
首先,我要求用户输入(不重要)
然后,我使用将输入放入字符串内创建输入提示 最后,尝试打印输入。stdout
stdin
stdin
rl
stdout
问题
如果你还记得工作原理。它会循环,直到找到 null 字节。
但是,它不会以 null 终止其输入。
因此,当找到输入的长度时,它不会在输入的末尾停止,而是会继续收集垃圾,直到遇到空字节。但到那时,你会得到如下所示的东西:strlen
stdin
stdout
What is your name?
Logan
Logan
��
@ @@%)@1+@7L@>a@O@J @V @]▒ @hello.asmstrrlstrlenstrlen_nextstrlen_nullstdinstdoutExitSuccess__bss_start_edata_end.symtab.strtab.shstrtab.text.datam! @ ▒ P
▒ h!b�!'
简单的解决方法应该是在字符串末尾添加一个 null 字节。我该怎么做呢?另外,正如我一开始所说,组装非常新......所以你花哨的行话只会吓到我。
完整程序
global _start
section .data
str: db "What is your name?", 10, 0 ; db: Define byte (8 bits)
; Declare hw to be "Hello, world!"
; 10 = "\n" (newline character)
rl: db 0
section .text
_start:
mov rsi, str
call stdout ; Write to STDOUT
call stdin
call stdout
jmp ExitSuccess ; Return with exit code 0
strlen:
push rsi ; Save string to stack
strlen_next:
cmp [rsi], byte 0 ; Compare char to null byte
jz strlen_null ; Jump if null byte
inc rcx ; Char wasn't null, increment rcx (string length)
inc rsi ; Next char
jmp strlen_next ; Repeat, until we get a null byte
strlen_null:
pop rsi ; Load string from the stack
ret ; Return to call
stdin:
mov rax, 0
mov rdi, 0
mov rsi, rl
mov rdx, strlen
syscall
ret
stdout:
mov rax, 1 ; syscall write
mov rdi, 1 ; File descriptor STDOUT
call strlen ; String length of rsi
mov rdx, rcx ; Move strlen of rsi into rdx
syscall
ret
ExitSuccess:
mov rax, 60 ; syscall exit
mov rdi, 0 ; Move exit code into rdi
syscall
答:
1赞
Logan Seeley
9/16/2023
#1
必须进行两项更改:
首先,我需要修复该功能。(我已重命名为)
问题是我在开始计数之前忘记设置为零,
最后,在我们执行 in 之后,我们直接在缓冲区的末尾添加一个 null 字节。(strlen
_strlen
rcx
syscall
stdin
mov byte [rsi+rax-1], 0
)
;##################################################################################################################################
;
; hello.asm
;
; 15/09/2023
;
; Logan Seeley
;
; Compiling:
; Create object file: $ nasm -f elf64 hello.asm
; Link object file: $ ld -m elf_x86_64 hello.o -o hello
; Execute file: $ ./hello
; Print exit code: $ printf "\n[Process finished with exit code $?]\n"
;
; All in one command:
; $ nasm -f elf64 hello.asm; ld -m elf_x86_64 hello.o -o hello; ./hello; printf "\n\n[Process finished with exit code $?]\n"
;
; TODO: Use call-preserved registers RBX, RBP, RSP, and R12-R15 for functions instead of using RDI, RSI, RDX, RCX, R8, R9
; RBX, RBP and RSP should be used for args
; R12-15 should be used for return values
;
;##################################################################################################################################
global _start
section .data
prompt: db "What is your name?", 10, 0 ; db: Define byte (8 bits)
; Declare prompt to be "What is your name?"
; 10 = "\n" (newline character)
; 0 = null-byte (for zero-termination)
hello: db "Hello, ", 0
bufsize equ 2048
rl: resb bufsize
section .text
_start:
mov rsi, prompt ; Move string into rsi to be written to STDOUT
call stdout ; Write to STDOUT
call stdin
mov rsi, hello
call stdout
mov rsi, rl ; Move input into rsi
call stdout
jmp ExitSuccess ; Return with exit code 0
_strlen:
xor rcx, rcx ; Move 0 into rcx so we count up from 0
push rsi ; Save string to stack
strlen_next:
cmp [rsi], byte 0 ; Compare char to null byte
jz strlen_null ; Jump if null byte
inc rcx ; Char wasn't null, increment rcx (string length)
inc rsi ; Next char
jmp strlen_next ; Repeat, until we get a null byte
strlen_null:
pop rsi ; Load string from the stack
ret ; Return to call
stdin:
mov rax, 0
mov rdi, 0
mov rsi, rl
mov rdx, _strlen
syscall
mov byte [rsi+rax-1], 0 ; Setting last character of buffer to null-byte
; Replacing last character instead of concatting,
; because last character is a newline from user pressing enter
ret
stdout:
mov rax, 1 ; syscall write
mov rdi, 1 ; File descriptor STDOUT
call _strlen ; String length of rsi
mov rdx, rcx ; Move strlen of rsi into rdx
syscall
ret
ExitSuccess:
mov rax, 60 ; syscall exit
mov rdi, 0 ; Move exit code into rdi
syscall
评论
1赞
Peter Cordes
9/16/2023
rl: db 0
仅保留 1 个字节的空间。您告诉系统调用读取等于 地址的字节数。选择缓冲区大小并使用它,例如 和 。无论如何,您的代码恰好可以工作,因为它后面没有任何内容,并且内存保护具有页面粒度。read
_strlen
bufsize: equ 2048
rl: resb bufsize
.data
0赞
Peter Cordes
9/16/2023
此外,与仅返回长度并将其传递给下一个调用相比,存储一个零以便稍后可以去查找它是非常低效的。但是,当然,如果您只想有一个全局变量而没有 args 或返回值,则可以这样做,您可以将缓冲区视为 C 字符串。如果要在第一个字节处切断二进制数据,则用户从二进制文件或管道重定向输入。char rl[]
0
0赞
Peter Cordes
9/16/2023
哦,等等,它并没有避免函数之间的参数或返回值; 取决于 RSI 仍然指向缓冲区。所以你基本上是在编写一个大函数,但使用 .如果您在设置 RSI 时将顶级代码指向而不是随意这样做,那么这将是不太糟糕的设计。stdout:
_start
call
_start
rl
stdin
0赞
Logan Seeley
9/16/2023
如何避免函数之间的参数和返回值?我是组装新手,所以我对常见做法和良好的设计知之甚少,
0赞
Peter Cordes
9/16/2023
你不应该避免这一点;就像在高级编程语言中一样,如果你要编写函数,它们应该通过参数和返回值进行通信,而不仅仅是通过全局变量。(标准的函数调用约定与系统调用约定非常相似,RDI、RSI、RDX、RCX、R8、R9 中的 args 按此顺序排列(如果有那么多),并在 RAX 中返回值。调用保留的注册是 RBX、RBP、RSP 和 R12-R15。自定义调用约定(如 _strlen)在 asm 中是可以的,只要您注释它们即可。
评论
sys_read
mov byte [rsi+rax], 0
系统调用后,由于指针仍在 RSI 中。至少在检查 RAX 是否为非负数之后(因为这将表明错误返回,就像在关闭 stdin 的情况下运行程序一样。-EBADF
write
mov rdx, rax
strlen
strlen
syscall
syscall