汇编中的(进位标志)和系统调用之间有什么关系(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
即使没有错误,只是因为 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
returns 0
使用我的 ft_read
当没有错误时,它 returns出现错误时相应的错误编号。
但问题是我不知道为什么 carry flag
被设置了,当没有 cmp
时,系统调用是否设置了 carry flag
时出现错误电话,还是背景中发生了另一件事?我想详细解释一下系统调用和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);
}
部分混淆是术语“系统调用”用于两个真正不同的事物:
内核读取文件的实际请求,通过执行 syscall
指令调用。
用户提供的 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 位值中的低位(即进位标志所在的位置)它变成 r11
为 sysret
做准备。然后当你的进程继续执行你的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 中有更多信息。我希望我能引用一些更权威的文档,但我不知道在哪里可以找到它。这里的内容似乎是“常识”。
我是汇编语言的新手,我必须在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
即使没有错误,只是因为 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
returns 0
使用我的 ft_read
当没有错误时,它 returns出现错误时相应的错误编号。
但问题是我不知道为什么 carry flag
被设置了,当没有 cmp
时,系统调用是否设置了 carry flag
时出现错误电话,还是背景中发生了另一件事?我想详细解释一下系统调用和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);
}
部分混淆是术语“系统调用”用于两个真正不同的事物:
内核读取文件的实际请求,通过执行
syscall
指令调用。用户提供的 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 位值中的低位(即进位标志所在的位置)它变成 r11
为 sysret
做准备。然后当你的进程继续执行你的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 中有更多信息。我希望我能引用一些更权威的文档,但我不知道在哪里可以找到它。这里的内容似乎是“常识”。