首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >(x64标志)和syscall在程序集中( Mac上的英特尔语法)之间有什么关系?

(x64标志)和syscall在程序集中( Mac上的英特尔语法)之间有什么关系?
EN

Stack Overflow用户
提问于 2020-11-13 11:48:37
回答 1查看 655关注 0票数 4

我对汇编语言很陌生,我必须在朗读中使用汇编语言x64实现一个MAC函数。到目前为止,这就是我所做的:

代码语言:javascript
复制
;;;;;;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的返回值与103进行比较,我将解释为什么我将其与103进行比较,但在此之前,让我解释其他一些内容,即耶诺 (mac手册页),这是关于errno的手册页中的内容。

当系统调用检测到错误时,它返回一个指示失败的整数值(通常为-1),并相应地设置变量errno。<这允许在接收a-1时解释失败,并采取行动,accordingly.>成功的调用永远不会设置errno;一旦设置,它将一直保持到另一个错误发生。只有在出错后才能检查。请注意,一些系统调用重载了这些错误号的含义,并且必须根据调用的类型和情况来解释这些错误号的含义。 下面是中给出的错误及其名称的完整列表。 0错误0。不用了。 不允许1 EPERM操作。尝试执行仅限于具有适当权限的进程或文件或其他资源所有者的操作。 2 ENOENT没有这样的文件或目录。指定路径名的组件不存在,或者路径名是空字符串。 ..................................................I'll跳过这一部分(我写了这行btw).................................................. 101 ETIME流ioctl()超时。此错误保留给以后使用。 套接字上不支持102 EOPNOTSUPP操作。引用的套接字类型不支持尝试操作;例如,尝试接受数据报套接字上的连接。

正如我所理解的,并且在我使用lldb调试了很多时间之后,我注意到syscall返回errno手册页面中显示的一个数字,例如,当我使用下面的main.c代码在ft_read函数中传递一个糟糕的文件描述符时:

代码语言:javascript
复制
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函数时,如下面的main.c所示,我们的read syscall返回"the number of bytes read is returned",这就是read syscall在手册中描述的返回的内容:

在成功的情况下,返回读取的字节数(零表示文件的结束),文件位置由这个数字前进。如果这个数字小于请求的字节数,这并不是一个错误;这可能是因为现在实际可用的字节较少(可能是因为我们接近文件末尾,或者是因为我们正在从管道或终端读取数据),或者因为read()被信号中断了。另见说明。 如果出现错误,则返回-1,并适当设置errno。在这种情况下,未指定文件位置(如果有的话)是否更改。

在我的主要工作原理中,我把一个很好的文件描述符、一个存储数据的缓冲区和50个字节传递给我的ft_read函数,所以syscall将返回存储在rax中的50,然后比较使它的作业>> rax = 50 < 103,即使没有错误,它也会跳转到ft_read_error,仅仅因为50是不存在的errno错误数之一。

有人建议使用jc (如果设置了进位标志就跳),而不是使用jl (如果少跳的话),如下代码所示:

代码语言:javascript
复制
;;;;;;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时,syscall是在调用过程中出现错误时设置carry flag,还是后台发生了另一件事情?我想要一个关于syscall和carry flag之间关系的详细解释,我对组装仍然是个新手,我非常想学习它,并且提前感谢。

syscall carry flag 之间的关系是什么? syscall 是如何设置的?

这是我的main.c函数,用于编译上面的汇编代码:

代码语言:javascript
复制
#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);
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-11-13 23:10:01

混淆的部分原因是,“系统调用”一词用于两件真正不同的事情:

  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指令将控制权传输回用户空间。该指令的功能之一是从rflags恢复r11寄存器。当系统调用开始时,内核将保存进程的原始rflags,因此它只需在加载到r11中准备sysret之前或之后设置或清除这个64位值中的低阶位(这是进位标志所在的位置)。然后,当您的进程继续执行您的syscall之后的指令时,进位标志将处于相应的状态。

cmp指令当然是x86 CPU可以设置x86标志的方式之一,但这绝不是唯一的方法。即使是这样,在用户空间程序中看不到这段代码也不足为奇,因为它是决定如何设置它的内核。

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

代码语言:javascript
复制
    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位MacOS程序集的syscall文档有更多的信息。我希望我能引用一些更权威的文件,但我不知道在哪里可以找到它。这里似乎是“常识”。

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64820365

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档