汇编中的(进位标志)和系统调用之间有什么关系(Mac Os 上的 x64 Intel 语法)?

What is the relation between (carry flag) and syscall in assembly (x64 Intel syntax on Mac Os)?

我是汇编语言的新手,我必须在MAC中使用汇编语言x64实现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,然后我将rax中存储的read syscall的returned值与103进行比较,我将解释为什么我比较它使用 103 但在此之前,让我解释一下其他内容,即 errno(mac 的手册页),这是手册页中关于 errno 的内容:

When a system call detects an error, it returns an integer value indicat-ing indicating ing failure (usually -1) and sets the variable errno accordingly. <This allows interpretation of the failure on receiving a -1 and to take action accordingly.> Successful calls never set errno; once set, it remains until another error occurs. It should only be examined after an error. Note that a number of system calls overload the meanings of these error numbers, and that the meanings must be interpreted according to the type and circumstances of the call.

The following is a complete list of the errors and their names as given in <sys/errno.h>.

0 Error 0. Not used.

1 EPERM Operation not permitted. An attempt was made to perform an operation limited to processes with appropriate privileges or to the owner of a file or other resources.

2 ENOENT No such file or directory. A component of a specified pathname did not exist, or the pathname was an empty string.

..................................................I'll skip this part (I wrote this line btw)..................................................

101 ETIME STREAM ioctl() timeout. This error is reserved for future use.

102 EOPNOTSUPP Operation not supported on socket. The attempted operation is not supported for the type of socket referenced; for example, trying to accept a connection on a datagram socket.

据我所知,在我使用 lldb 调试了很多时间后,我注意到 syscall return 是 [=20] 中显示的那些数字之一=] 手册页,例如,当我传递一个错误的文件描述符时,在我的 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 returns 9 存储在 rax 所以我比较 if rax < 103 (因为 errno 值是从 0 到102) 然后跳转到 ft_read_error 因为这是它应该做的。

好吧,一切都按我的计划进行,但是有一个不知从何而来的问题,当我打开一个现有文件并将其文件描述符传递给我的 ft_read 函数时,如下所示 main.c,我们读 syscall returns "the number of bytes read is returned",这就是手册中描述的 read 系统调用 returns:

On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number. It is not an error if this number is smaller than the number of bytes requested; this may happen for example because fewer bytes are actually available right now (maybe because we were close to end-of- file, or because we are reading from a pipe, or from a terminal), or because read() was interrupted by a signal. See also NOTES.

On error, -1 is returned, and errno is set appropriately. In this case, it is left unspecified whether the file position (if any) changes.

在我看来它工作得很好,我向我的 ft_read 函数传递了一个良好的文件描述符、一个用于存储数据的缓冲区和要读取的 50 个字节,因此 syscall 将return 50 存储在 rax 中,然后比较它的工作 >> rax = 50 < 103 然后它会跳转到 ft_read_error 即使没有错误,只是因为 50errno 错误编号之一,在这种情况下不是。

有人建议使用 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 returns 0 使用我的 ft_read 当没有错误时,它 returns出现错误时相应的错误编号。

但问题是我不知道为什么 carry flag 被设置了,当没有 cmp 时,系统调用是否设置了 carry flag 时出现错误电话,还是背景中发生了另一件事?我想详细解释一下系统调用和carry flag之间的关系,我对汇编还是个新手,非常想学习它,在此先感谢。

syscallcarry 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);
}

部分混淆是术语“系统调用”用于两个真正不同的事物:

  1. 内核读取文件的实际请求,通过执行 syscall 指令调用。

  2. 用户提供的 C 函数 read()space C 库作为 C 程序方便地访问 #1 功能的一种方式。

手册页记录了如何使用#2,但在汇编中您使用的是#1。整体语义相同,但访问方式的细节不同。

特别是,C 函数 (#2) 遵循这样的约定,即通过从函数 returning -1 并设置变量 errno 来指示错误。但是,这不是#1 指示错误的便捷方式。 errno 是位于程序内存中某处的全局(或线程局部)变量;内核不知道在哪里,告诉它会很尴尬,所以内核不能轻易直接写这个变量。内核以其他方式 return 错误代码更简单,并将其留给 C 库来设置 errno 变量。

基于BSD的操作系统通常遵循的约定是内核系统调用(#1)会根据是否发生错误来设置或清除进位标志。如果没有发生错误,rax 包含系统调用的 return 值(这里是读取的字节数);如果确实发生错误,eax 包含错误代码(它通常是一个 32 位值,因为 errno 是一个 int)。因此,如果您正在用汇编语言编写,那是您应该期望看到的。

至于内核如何管理set/clear进位标志,当系统调用完成时,内核执行sysret指令将控制权交还给用户space。该指令的功能之一是从r11恢复rflags寄存器。当系统调用开始时,内核将保存您进程的原始 rflags,因此它只需在加载之前或之后设置或清除此 64 位值中的低位(即进位标志所在的位置)它变成 r11sysret 做准备。然后当你的进程继续执行你的syscall后面的指令时,进位标志将处于相应的状态。

cmp 指令当然是 其中一个 x86 CPU 可以设置进位标志的方式,但它绝不是 方式。即使是这样,您在 userspace 程序中看不到该代码也不足为奇,因为它是内核决定如何设置它的。

为了实现#2,C 库的 read() 函数需要在内核约定(#1)和 C 程序员期望的(#2)之间建立接口,因此他们必须编写一些检查进位标志并在需要时填充 errno 的代码。他们的此功能代码可能如下所示:

    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

64-bit syscall documentation for MacOS assembly 中有更多信息。我希望我能引用一些更权威的文档,但我不知道在哪里可以找到它。这里的内容似乎是“常识”。