我的cat函数是否存在潜在的错误?

时间:2019-02-11 23:47:48

标签: assembly nasm x86-64 cat

我正在Mac电脑上运行此程序。创建此函数已经7个月了,有人在争论我没有正确地创建cat函数。我想知道如果我调用其他函数为什么会不起作用。我的输出是正确的,但是我不记得为什么不起作用的原因。

前一段时间,我决定重新创建某些功能,但是要在NASM中。为了测试我的知识,我想使用open函数重新创建从源文件路径接收文件描述符的cat调用。然后调用我的cat(fd)汇编函数。输出看起来令人满意,但在我的辩护中,标签似乎不正确。

这是我的汇编猫功能文件cat.s

[bits 64]

global cat
%define SYS_READ 0x2000003
%define SYS_WRITE 0x2000004
%define STDOUT 0x01
%define BUFF_SIZE 0xff

section         .bss
    buffer      resb        BUFF_SIZE       ; unitialised storage space, basically reserving BUFF_SIZE bytes

section         .text

; int               my_cat(int fd);
_my_cat:
    xor     rax, rax
    push    rbp
    mov     rbp, rsp

    .read:
        push    rdi                 ; push rdi stack first before we start reading
        lea     rsi, [rel buffer]
        mov     rdx, BUFF_SIZE
        mov     rax, SYS_READ       ; read
        syscall
        jc      end                 ; jump carry
        cmp     rax, 0
        jle     end

    .write:                         ; write the message indicating end of file write
        mov     rdi, STDOUT         ; output fd
        mov     rdx, rax            ; store the destination of rax
        mov     rax, SYS_WRITE      ; write
        syscall
        pop     rdi                 ; take out our initial rdi stack
        jmp     .read               ; read again

    end:
        mov     rsp, rbp
        pop     rbp
        ret

然后这是我在open()文件中hello.s之后运行的测试文件:

[bits 64]

global my_hello

section .data
    hello_world db 'Hello World!', 0x0a

section .text

%define SYS_WRITE 0x2000004

%define SYS_EXIT 0x2000001

_my_hello:
    mov     rax, SYS_WRITE      ; syscall write
    mov     rdi, 1              ; stdout fd (where will it write?)
    mov     rsi, hello_world    ; string address (where does it start?)
    mov     rdx, 20             ; string length in bytes (how many bytes to write?)
    syscall                     ; system call

    mov     rax, SYS_EXIT       ; exit system call
    xor     rdi, 0              ; 0 can be replaced with rdi
    syscall

main.c文件:

extern  int             my_hello(void);
extern  int             my_cat(int fd);

int main(void)
{
    printf("testing my_cat: \n");
    fd = open("src/my_hello.s", O_RDONLY);
    ft_cat(fd);

    return (0);
}

我有正确的期望值,它应该是hello.s文件的内容。我唯一不了解的部分是我没有正确使用write函数,因为我没有使用3个参数(描述符,字符串的内容,nbuff_size)。帮我学习。例如:编译后如何查看幕后情况(例如为我的cat.s使用lldb?)移动寄存器并不能帮助我了解更多。

1 个答案:

答案 0 :(得分:1)

您的微小缓冲区使此超级低效(内核/用户转换曾经为255个字节)。至少8kiB会更好,也许是32至64k。 (适合典型的现代x86的L2缓存大小。)

您可能还需要处理返回EINTR(在读取任何数据之前被信号中断)的读或写操作,以确保正确性,以防用户在正确的时机击中control-z。我不确定系统调用语义在这里是如何工作的,或者仅用于带有处理程序的信号。但是,如果这应该是一个函数,而不是整个程序,则不能假定调用方没有设置任何处理程序。

至少您将read的返回值用作write的长度,所以正确处理了短读(返回早,但长度非零)。

我想可以安全地假设/要求未使用O_NONBLOCK打开输入FD和stdout,因为您不处理EWOULDBLOCK / EAGAIN。但这是正常现象,而不是cat()中的错误,只是合同中的一部分。


代码质量:正如@phuclv所指出的那样,您浪费的是xor rax,rax而不是xor eax,eax的代码字节。而且这毫无意义,因为mov eax, SYS_READ已经覆盖了整个RAX。

push rdi也很奇怪。使用r8之类的寄存器或其他东西来存放fd函数arg。 syscall仅阻塞RCX,R11和RAX中的返回值。您无需在系统调用之间接触用户空间堆栈内存;根据内核的Meltdown缓解策略,这可能导致额外的TLB丢失或页面错误。 (不过,push / pop是最小的代码大小选项。)


为此,OS X是否没有从fd到fd的复制系统调用?

Linux具有sendfile(2),可以在内核空间的FD之间进行复制,从而避免了将数据复制到用户空间和返回到用户空间。 (它最初是为零拷贝Web /文件服务器设计的,用于将文件中的数据发送到套接字,并且在真正的旧内核(2.6.33之前)中,out_fd必须是套接字。但这是几年前的事情。)无论如何,任意大的副本大小都不会分配/页面破坏任何用户空间内存,也不会保存副本。

特别是对于文件,还有copy_file_range(2),它使内核/文件系统驱动程序有机会进行诸如NFS服务器端复制之类的操作,或与支持该功能的系统进行写时复制的reflink。 (Sendfile也许也可以做到这一点,但是手册页中没有提到它。)

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

ssize_t copy_file_range(int fd_in, loff_t *off_in,
                           int fd_out, loff_t *off_out,
                           size_t len, unsigned int flags);