我是汇编语言的新手,我需要使用汇编语言x64
在MAC
上实现read函数。
到目前为止,这是我做的:
;;;;;;ft_read.s;;;;;;
global _ft_read:
section .text
extern ___error
_ft_read:
mov rax, 0x2000003 ; store syscall value of read on rax
syscall ; call read and pass to it rdi , rsi, rdx ==> rax read(rdi, rsi, rdx)
cmp rax, 103 ; compare rax with 103 by subtracting 103 from rax ==> rax - 103
jl _ft_read_error ; if the result of cmp is less than 0 then jump to _ft_read_error
ret ; else return the rax value which is btw the return value of syscall
_ft_read_error:
push rax
call ___error
pop rcx
mov [rax], rcx
mov rax, -1
ret
如上所述,我使用系统调用 syscall 来调用 read 函数,然后将 syscall 返回的 rax 存储的值与
103
进行比较。我将在解释为什么要与 103
进行比较之前,解释一些其他内容,即关于 errno(mac 的 man 手册),以下是有关 errno
的手册页面中的描述:
当系统调用检测到错误时,它会返回一个整数值,表示失败(通常为 -1),并相应地设置变量 errno。
以下是错误及其名称的完整列表,如 <sys/errno.h> 中所示。
0
错误 0。未使用。
1
EPERM 不允许操作。尝试执行仅限于具有适当权限的进程或文件或其他资源所有者的操作。
2
ENOENT 没有此文件或目录。指定路径名的某个组成部分不存在,或该路径名为空字符串。..................................................我将跳过此部分(顺便说一下,这行是我写的)..................................................
101
ETIME 流 ioctl() 超时。此错误保留供将来使用。
102
EOPNOTSUPP 不支持套接字上的操作。所引用套接字类型不支持尝试的操作;例如,在数据报套接字上尝试接受连接。
经过我调试了很长时间,使用 lldb
我注意到,syscall
返回的数字是在 errno
man 手册中显示的数字之一,例如,当我传递一个坏的文件描述符时,在我的 ft_read
函数中使用以下 main.c
代码:
int bad_file_des = -1337;// a file descriptor which it doesn't exist of course, you can change it with -42 as you like
ft_read(bad_file_des, buff, 300);
我们的syscall
返回值为9
,存储在rax
中,因此我要比较rax
<103(因为errno值从0到102),然后跳转到ft_read_error
,因为这是应该做的。
好的,一切都按照我计划的进行,但是突然出现了一个问题,当我打开一个现有文件并将它的文件描述符传递给下面的main.c
中所示的ft_read
函数时,我们的read
syscall
返回值为"返回读取的字节数"
,这就是手册中描述的read
syscall
返回值:
成功时,返回读取的字节数(零表示文件结束),文件位置将向前移动此数字。如果此数字小于请求的字节数,则不会发生错误;例如,可能由于当前实际上可用的字节较少(可能是因为我们接近文件末尾或者因为我们正在从管道或终端读取)或者read()被信号中断而发生这种情况。也请参见NOTES。
出错时返回-1,errno相应设置。在这种情况下,文件位置(如有)是否更改是未指定的。
在我的主程序中,当我将一个好的文件描述符、一个缓冲区以及50个字节的数据传递给我的ft_read
函数时,它的工作非常顺利。因此,syscall
将返回50
并存储在rax
中,然后比较完成了其任务>> rax=50
< 103,因此它会跳转到ft_read_error
,即使没有错误,只是因为50
是这些不适用于此案例的errno
错误号之一。
有人建议使用jc
(设置进位标志后跳转)而不是jl
(如果小于则跳转),如下所示:
;;;;;;ft_read.s;;;;;;
global _ft_read:
section .text
extern ___error
_ft_read:
mov rax, 0x2000003 ; store syscall value of read on rax
syscall ; call read and pass to it rdi , rsi, rdx ==> rax read(rdi, rsi, rdx)
; deleted the cmp
jc _ft_read_error ; if carry flag is set then jump to _ft_read_error
ret ; else return the rax value which is btw the return value of syscall
_ft_read_error:
push rax
call ___error
pop rcx
mov [rax], rcx
mov rax, -1
ret
猜猜怎么着,当没有错误时使用我的ft_read
,它能完美运行并返回0
的errno
,而当有错误时返回相应的错误号码。
但问题在于,当没有cmp
时,我不知道为什么carry flag
被设置了,是syscall在调用期间发生错误时设置了carry flag
,还是背景中发生了其他事情?我想要详细解释syscall和carry flag
之间的关系,我还是新手汇编,非常渴望学习,提前感谢。
syscall
和carry flag
之间的关系以及syscall
如何设置它?
这是我使用来编译上面汇编代码的main.c
函数:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
ssize_t ft_read(int fildes, void *buf, size_t nbyte);
int main()
{
/*-----------------------------------------------------------------------*/
///////////////////////////////////////////////////////////////////////////
/********************************ft_read**********************************/
int fd = open("./main.c", O_RDONLY);
char *buff = calloc(sizeof(char), 50 + 1);
int ret = ft_read(fd, buff, 50);
printf("ret value = %d, error value = %d : %s\n", ret, errno, strerror(errno));
//don't forget to free ur buffer bro, this is just a test main don't be like me.
return (0);
}
stc
和它的相反指令clc
被近距离调用的函数用来通过进位标志指示错误或成功。(中断函数处理程序需要修改iret
堆栈帧,在堆栈上设置或清除调用者的CF。) - ecmsyscall
进行的真实系统调用的包装器。errno
是CRT的一部分。至少在Linux下是这样工作的,可能在Darwin下也是如此。 Linux的系统调用在出错时返回负值,并从这些负值设置errno。 您应该检查Darwin的系统调用ABI而不是man。 - Margaret Bloom