【发布时间】:2021-07-12 15:15:49
【问题描述】:
我在汇编中编写了以下cat 的简化实现。它使用 linux 系统调用,因为我正在运行 linux。代码如下:
.section .data
.set MAX_READ_BYTES, 0xffff
.section .text
.globl _start
_start:
movq (%rsp), %r10 # save the value of argc somewhere else
movq 16(%rsp), %r9 # save the value of argv[1] somewhere else
movl $12, %eax # syscall 12 is brk. see brk(2)
xorq %rdi, %rdi # call with 0 as first arg to get current end of memory
syscall
movq %rax, %r8 # this is the address of the current end of memory
leaq MAX_READ_BYTES(%rax), %rdi # let this be the new end of memory
movl $12, %eax # syscall 12, brk
syscall
cmp %r8, %rax # compare the two; if the allocation failed, these will be equal
je exit
leaq -MAX_READ_BYTES(%rax), %r13 # store the start of the free area in %r13
movq %r10, %rdi # retrieve the value of argc
cmpq $0x01, %rdi # if there are no cli args, process stdin instead
je stdin
# open the file
movl $0x02, %eax # syscall #2 = open.
movq %r9, %rdi
movl $0, %esi # second argument: flags. 0 means read-only.
xorq %rdx, %rdx # this argument isn't used here, but zero it out for peace of mind.
syscall # returns the file descriptor number in %rax
movl %eax, %edi
movl %edi, %r12d # first argument: file descriptor.
call read_and_write
jmp cleanup
stdin:
movl $0x0000, %edi # first argument: file descriptor.
movl %edi, %r12d # first argument: file descriptor.
call read_and_write
jmp cleanup
read_and_write:
# read the file.
movl $0, %eax # syscall #0 = read.
movl %r12d, %edi
movq %r13 /* pointer to allocated memory */, %rsi # second argument: address of a writeable buffer.
movl $MAX_READ_BYTES, %edx # third argument: number of bytes to write.
syscall # num bytes read in %rax
movl %eax, %r15d
# print the file
movl $1, %eax # syscall #1 = write.
movl $1, %edi # first argument: file descriptor. 1 is stdout.
movq %r13, %rsi # second argument: address of data to write.
movl %r15d, %edx # third argument: number of bytes to write.
syscall # result ignored.
cmpq $MAX_READ_BYTES, %r15
je read_and_write
ret
cleanup:
# close the file
movl $0x03, %eax # syscall #3 = close.
movl %r14d, %edi # first arg: file descriptor number.
syscall # result ignored.
exit:
# set the exit code
movl $60, %eax # syscall #60 = exit.
movq $0, %rdi # exit 0 = success.
syscall
我已经将它组装成一个名为asmcat 的ELF 二进制文件。为了测试这个程序,我得到了文件/tmp/random:
$ wc -c /tmp/random
94870 /tmp/random
当我运行以下,结果是一致的:
$ ./asmcat /tmp/random | wc -c
94870
这是同一命令的两次单独运行:
$ cat /tmp/random | ./asmcat | wc -c
65536
$ cat /tmp/random | ./asmcat | wc -c
94870
将输出重定向到文件会始终生成相同大小的文件:
for i in {0..25}; do
cat /tmp/random | ./asmcat > /tmp/asmcat-output-$i
done
for i in {0..25}; do
wc -c /tmp/asmcat-output-$i
done
所有生成的文件都具有相同的大小,94870。这让我相信到wc 的管道是导致不一致行为的原因。我的程序应该做的就是一次读取标准输入,65535 个字节,然后写入标准输出。程序中可能存在错误,但是,为什么它会始终重定向到大小一致的文件?所以我强烈的感觉是管道的某些东西导致我的汇编程序输出大小的测量不一致。
欢迎任何反馈,包括在汇编程序中采用的方法(我只是为了好玩/练习而写的)。
【问题讨论】:
-
您是否尝试在
strace下运行此程序以查看您正在进行的系统调用?看起来您遇到了单个写入管道的最大大小的限制。您的代码不会检查write的返回值以查看每次写入实际复制到输出 FD 的字节数。看起来它会在任何短读时退出,因此信号或 TTY 输入将是一个问题。此外,为了提高效率,最好将缓冲区设为 2 的幂,或者至少是页面大小 (4k) 的倍数。 -
感谢@PeterCordes,这是很多很好的反馈。我以前从未使用过
strace,但我使用gdb一步一步地运行它。我现在就玩strace。当使用堆栈作为缓冲区时,决定从哪里开始的最佳实践是什么?我是否应该将 argc 和 argv 弹出到寄存器中,然后只使用%rsp作为缓冲区的起始地址?或者我应该使用某种偏移量?我稍后会回来根据您的建议编辑我的帖子以清理它。 -
@PeterCordes 奇怪的是,我在运行
cat /tmp/random | strace ./asmcat | wc -c时似乎无法重现不良行为。它总是以正确的字节数结束。 -
是的,我在写答案时注意到了同样的事情。幸运的是,我能够解释原因:) 我最初的评论有一些不完全准确的猜测。
标签: linux assembly pipe x86-64 system-calls