[英]What is the relation between (carry flag) and syscall in assembly (x64 Intel syntax on Mac Os)?
我是汇编语言的新手,我必须在MAC
中使用汇编语言x64
实现read function 。 到目前为止,这就是我所做的:
;;;;;;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
正如你在上面看到的,我用系统调用调用读取,然后我将存储在 rax 中的读取系统调用的返回值与103
进行比较,我将解释为什么我将它与103
进行比较,但在此之前,让我解释一下其他内容,即errno (mac 的手册页),这是关于errno
的手册页中写的内容:
当系统调用检测到错误时,它会返回一个 integer 值,指示失败(通常为 -1)并相应地设置变量 errno。 <这允许在收到 -1 时解释失败并采取相应的行动。> 成功的调用永远不会设置 errno; 一旦设置,它将一直保留到另一个错误发生。 仅应在出错后对其进行检查。 请注意,许多系统调用会重载这些错误编号的含义,并且必须根据调用的类型和环境来解释这些含义。
以下是 <sys/errno.h> 中给出的错误及其名称的完整列表。
0
错误 0。未使用。
1
EPERM 不允许操作。 试图执行仅限于具有适当权限的进程或文件或其他资源的所有者的操作。
2
ENOENT 没有这样的文件或目录。 指定路径名的组件不存在,或者路径名是空字符串。.....................................................我将跳过这部分(顺便说一句,我写了这一行)...................................... ........................
101
ETIME STREAM ioctl() 超时。 此错误保留供将来使用。
102
EOPNOTSUPP 套接字不支持操作。 引用的套接字类型不支持尝试的操作; 例如,尝试接受数据报套接字上的连接。
据我所知,在我使用lldb
调试了很多时间之后,我注意到syscall
返回了errno
手册页中显示的那些数字之一,例如,当我传递一个错误的文件描述符时,在我的ft_read
function 中使用下面的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
返回存储在rax
中的9
,所以我比较rax
< 103(因为 errno 值从 0 到 102)然后跳转到ft_read_error
,因为这是它应该做的。
好吧,一切都按我的计划进行,但是有一个问题无处不在,当我打开一个现有文件并将它的文件描述符传递给我的ft_read
function 如下所示main.c
时,我们的读取syscall
返回"the number of bytes read is returned"
,这是read
系统调用返回的内容,如手册中所述:
成功时,返回读取的字节数(零表示文件末尾),文件 position 向前移动这个数字。 如果此数字小于请求的字节数,则不是错误; 例如,这可能是因为现在实际可用的字节较少(可能是因为我们接近文件末尾,或者因为我们正在从 pipe 或终端读取),或者因为 read() 被中断信号。 另见注释。
出错时返回 -1,并适当设置 errno。 在这种情况下,未指定文件 position(如果有)是否更改。
在我看来它工作得很好,我传递给我的ft_read
function 一个好的文件描述符,一个用于存储数据的缓冲区,以及要读取的 50 个字节,因此syscall
将返回存储在rax
中的50
个字节,然后比较就可以了>> 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
你猜怎么着,它工作得很好,当没有错误时, errno
使用我的ft_read
返回0
,当出现错误时,它返回适当的错误号。
但问题是我不知道为什么carry flag
被设置,当没有cmp
时,系统调用是否在调用过程中出现错误时设置carry flag
,或者后台发生了另一件事? 我想要一个关于 syscall 和carry flag
之间关系的详细解释,我还是汇编的新手,我非常想学习它,在此先感谢。
syscall
和carry flag
之间的关系是什么以及系统syscall
如何设置它?
这是我用来编译上面的汇编代码的main.c
function:
#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);
}
部分混淆是术语“系统调用”用于两件真正不同的事情:
实际请求 kernel 从文件中读取,通过执行syscall
指令调用。
C function read()
,由用户空间 C 库提供,作为 C 程序方便地访问#1 功能的一种方式。
手册页记录了如何使用#2,但在汇编中您使用的是#1。 整体语义相同,但访问它们的方式的细节不同。
特别是, C function (#2) 遵循从 function 返回-1
并设置变量errno
来指示错误的约定。 但是,这不是#1 指示错误的便捷方式。 errno
是位于程序 memory 某处的全局(或线程局部)变量; kernel不知道在哪,说出来也很别扭,所以kernel不能轻易直接写这个变量。 kernel 以其他方式返回错误代码更简单,并将其留给 C 库来设置errno
变量。
基于 BSD 的操作系统通常遵循的约定是 kernel 系统调用(#1)将根据是否发生错误来设置或清除进位标志。 如果没有发生错误, rax
包含系统调用的返回值(这里是读取的字节数); 如果确实发生错误,则eax
包含错误代码(它通常是一个 32 位值,因为errno
是一个int
)。 因此,如果您正在用汇编语言编写,那是您应该期望看到的。
至于kernel如何设置/清除进位标志,当系统调用完成时,kernel执行sysret
指令将控制权交还给用户空间。 该指令的功能之一是从r11
恢复rflags
寄存器。 kernel 将在系统调用开始时保存您进程的原始rflags
,因此它只需在将其加载到r11
之前或之后设置或清除此 64 位值中的低位(即进位标志所在的位置)为sysret
做准备。 然后当你的进程继续执行你的syscall
之后的指令时,进位标志将在相应的 state 中。
cmp
指令当然是x86 CPU 可以设置进位标志的方法之一,但这绝不是唯一的方法。 即使是这样,在用户空间程序中看不到该代码也不会让您感到惊讶,因为它是 kernel 决定了它的设置方式。
为了实现#2,C 库的read()
function 需要连接内核约定(#1)和 C 程序员所期望的(#2),因此他们必须编写一些代码来检查进位标志和如果需要,填充errno
。 他们针对这个 function 的代码可能如下所示:
global read
read:
mov rax, 0x2000003
; fd, buf, count are in rdi, rsi, rdx respectively
syscall
jc read_error
; no error, return value is in rax which is where the C caller expects it
ret
read_error:
; error occurred, eax contains error code
mov [errno], eax
; C caller expects return value of -1
mov rax, -1
ret
在MacOS 程序集的 64 位系统调用文档中有更多信息。 我希望我能引用一些更权威的文档,但我不知道在哪里可以找到它。 这里的内容似乎是“常识”。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.