x86_64 nasm read syscall 不是以 null 结尾的

x86_64 nasm read syscall isn't null-terminated

提问人:Logan Seeley 提问时间:9/16/2023 最后编辑:Peter CordesLogan Seeley 更新时间:9/16/2023 访问量:113

问:

x86_64 nasm read syscall 不是以 null 结尾的

刚开始组装。事实上,这是我的第一个hello world程序。 操作系统: Arch Linux (64-bit)

逻辑

我有三个功能。和。: 通过循环遍历字节直到找到 null 字节来查找字符串的长度。strlenstdinstdoutstrlen

stdin只需使用读取和写入系统调用即可。stdout

首先,我要求用户输入(不重要) 然后,我使用将输入放入字符串内创建输入提示 最后,尝试打印输入。stdoutstdinstdinrlstdout

问题

如果你还记得工作原理。它会循环,直到找到 null 字节。 但是,它不会以 null 终止其输入。 因此,当找到输入的长度时,它不会在输入的末尾停止,而是会继续收集垃圾,直到遇到空字节。但到那时,你会得到如下所示的东西:strlenstdinstdout

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

Linux 程序集 x86-64 nasm stdin

评论

3赞 harold 9/16/2023
顺便说一句,返回它读取的字节数sys_read
0赞 Tim Roberts 9/16/2023
右。如果你错过了,这就是答案。如果您知道读取了多少字节,则可以将该字节的零字节存储到缓冲区中。
0赞 Logan Seeley 9/16/2023
太好了,现在我该如何实现呢?因为如果我说实话。我只是在这里插上翅膀。
2赞 Peter Cordes 9/16/2023
mov byte [rsi+rax], 0系统调用后,由于指针仍在 RSI 中。至少在检查 RAX 是否为非负数之后(因为这将表明错误返回,就像在关闭 stdin 的情况下运行程序一样。-EBADF
1赞 Peter Cordes 9/16/2023
但是你的程序没有理由想要一个以零结尾的字符串;接下来要做的是对 stdout 的系统调用,这需要指针和长度。所以。另请注意,正常的 clobbers RAX 和 RDI,因为它遵循调用约定。你的没有,所以我建议不要打电话给它。你的坏了,因为它不会先将 RCX 归零,它只是将 RCX 递增长度。(RCX 将在您最后一次运行之后保存指令的地址,因为这是复制 RIP 的地方,因此内核将知道返回到哪里。writemov rdx, raxstrlenstrlensyscallsyscall

答:

1赞 Logan Seeley 9/16/2023 #1

必须进行两项更改: 首先,我需要修复该功能。(我已重命名为) 问题是我在开始计数之前忘记设置为零, 最后,在我们执行 in 之后,我们直接在缓冲区的末尾添加一个 null 字节。(strlen_strlenrcxsyscallstdinmov 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_strlenbufsize: equ 2048rl: resb bufsize.data
0赞 Peter Cordes 9/16/2023
此外,与仅返回长度并将其传递给下一个调用相比,存储一个零以便稍后可以去查找它是非常低效的。但是,当然,如果您只想有一个全局变量而没有 args 或返回值,则可以这样做,您可以将缓冲区视为 C 字符串。如果要在第一个字节处切断二进制数据,则用户从二进制文件或管道重定向输入。char rl[]0
0赞 Peter Cordes 9/16/2023
哦,等等,它并没有避免函数之间的参数或返回值; 取决于 RSI 仍然指向缓冲区。所以你基本上是在编写一个大函数,但使用 .如果您在设置 RSI 时将顶级代码指向而不是随意这样做,那么这将是不太糟糕的设计。stdout:_startcall_startrlstdin
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 中是可以的,只要您注释它们即可。