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

Hol*_*lon 3 c macos assembly errno system-calls

我是新来的汇编语言,我必须做出的实现读取用汇编语言功能x64MAC。到目前为止,这就是我所做的:

;;;;;;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
Run Code Online (Sandbox Code Playgroud)

你可以在上面看到,我调用读取系统调用,然后我比较了解系统调用存储在RAX与返回的值103,我将解释为什么我比较一下103,但在这之前,让我解释一下别的东西,这是错误号( mac 的手册页),这是在手册页中写的关于errno

当系统调用检测到错误时,它返回一个表示失败的整数值(通常为 -1)并相应地设置变量 errno。<这允许在收到 -1 时解释失败并采取相应的行动。> 成功的调用从不设置 errno;一旦设置,它会一直保持,直到发生另一个错误。只有在出现错误后才能对其进行检查。请注意,许多系统调用重载了这些错误编号的含义,并且必须根据调用的类型和环境来解释这些含义。

以下是 <sys/errno.h> 中给出的错误及其名称的完整列表。

0 错误 0。未使用。

1不允许 EPERM 操作。试图执行仅限于具有适当权限的进程或文件或其他资源的所有者的操作。

2ENOENT 没有这样的文件或目录。指定路径名的组成部分不存在,或者路径名是空字符串。

…………………………………………………………………………………………………………………………………………………………我将跳过这一部分(顺便说一句,我写了这一行)...................................... ………………

101ETIME STREAM ioctl() 超时。此错误保留供将来使用。

102EOPNOTSUPP 套接字不支持操作。引用的套接字类型不支持尝试的操作;例如,尝试接受数据报套接字上的连接。

据我了解,在使用 调试了很多时间后lldb,我注意到syscall返回errno手册页中显示的数字之一,例如,当我在我的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);
Run Code Online (Sandbox Code Playgroud)

我们的syscall返回9存储在rax所以我比较 if rax< 103(因为 errno 值是从 0 到 102)然后跳转到ft_read_error因为这是它应该做的。

好吧,一切都按我的计划进行,但是出现了一个问题,当我打开一个现有文件并将它的文件描述符传递给我的ft_read函数时,如下所示main.c,我们的读取syscall返回"the number of bytes read is returned",这就是read系统调用返回的内容,如上所述手动的:

成功时,返回读取的字节数(零表示文件结束),文件位置按此数字前进。如果此数字小于请求的字节数,则不是错误;例如,这可能是因为现在实际可用的字节较少(可能因为我们接近文件尾,或者因为我们正在从管道或终端读取),或者因为 read() 被一个中断信号。另见注释。

出错时,返回 -1,并适当设置 errno。在这种情况下,未指定文件位置(如果有)是否更改。

并且在我的主要内容中它工作得很好,我向我的ft_read函数传递了一个好的文件描述符,一个用于存储数据的缓冲区和 50 个要读取的字节,因此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
Run Code Online (Sandbox Code Playgroud)

猜猜看,它完美运行并在没有错误时使用 myerrno返回,并在出现错误时返回适当的错误号。0ft_read

但问题是我不知道为什么carry flag设置了,当没有时cmp,系统调用是否设置了carry flag在调用过程中出现错误时,或者后台发生了另一件事?我想要一个关于 syscall 和 之间关系的详细解释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);
}
Run Code Online (Sandbox Code Playgroud)

Nat*_*dge 5

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

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

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

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

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

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

至于内核如何设置/清除进位标志,当系统调用完成时,内核执行sysret指令将控制权转移回用户空间。该指令的功能之一是rflagsr11. 内核将rflags在系统调用开始时保存您的进程的原始值,因此它只需在加载之前或之后设置或清除此 64 位值中的低位(即进位标志所在的位置)r11以准备sysret. 然后当您的进程继续执行您的 之后的指令时syscall,进位标志将处于相应状态。

cmp指令无疑是一个的,一个x86的CPU可以设置进位标志的方式,但它决不是唯一的途径。即使是这样,在用户空间程序中看不到该代码也不会让您感到惊讶,因为内核决定了它的设置方式。

为了实现#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
Run Code Online (Sandbox Code Playgroud)

MacOS 程序集的 64 位系统调用文档中有更多信息。我希望我能引用一些更权威的文档,但我不知道在哪里可以找到它。这里的东西似乎是“常识”。