Linux系统64位AT&T系统调用汇编指令syscall

相关概念

在Linux中syscall系统调用(英文:system call)的指令。
想要深入了解syscall的作用,就需要了解特权级别。
现代计算机通常采用名为保护环(Protection Rings)的机制来保护整个系统的数据和功能,使其免受故障和外部恶意行为的伤害。这种方式通过提供多种不同层次的资源访问级别,即特权级别,来限制不同代码的执行能力。
Intel x86 架构中,特权级别被分为 4 个层次,即Ring0~Ring3。其中,Ring0层拥有最高特权,它具有对整个系统的最大控制能力,内核代码通常运行于此。相对地,Ring3层只有最低特权,这里是应用程序代码所处的位置。而位于两者之间的 Ring1Ring2层,则通常被操作系统选择性地作为设备驱动程序的“运行等级”。
Linux系统64位AT&T系统调用汇编指令syscall_第1张图片

根据特权级别的不同,CPU 能够被允许执行的机器指令、可使用的寄存器和可用的硬件资源也随之不同。比如位于Ring3层的应用程序,可以使用最常见的通用目的寄存器,并通过mov指令操作其中存放的数据。而位于 Ring0 层的内核代码则可以使用除此之外的cr0cr1等控制寄存器,甚至通过inout等机器指令,直接与特定端口进行 IO 操作。但如果应用程序尝试跨级别非法访问这些被限制的资源,CPU 将抛出相应异常,阻止相关代码的执行。
系统调用是操作系统提供的接口,逻辑上跟用户函数相似,能够帮助程序切换到进程的内核空间执行功能。也就是说,应用程序可以通过系统调用进入到操作系统内核空间完成某项功能。系统调用过程通常称为特权模式切换
Linux系统64位AT&T系统调用汇编指令syscall_第2张图片

系统调用一般函数(或者说用户函数)的最大区别在于,系统调用执行的代码位于操作系统底层的内核环境(内核环境也称作内核空间或应用程序的内核态,处于CPU特权等级Ring0)中,而用户函数代码则位于内核之上的应用环境(应用环境也称作用户空间或者应用程序的用户态,处于Ring3)中。
在使用系统调用时,rax寄存器里边需要放入系统调用号,表明需要执行的系统调用,/usr/include/asm/unistd_64.h可以看64位Linux系统调用和系统调用号的对应关系,可以使用man 2 系统调用查询如何使用系统调用,而系统调用就是在/usr/include/asm/unistd_64.h__NR_后边的字符串,比如readselectsocket等。
cat /usr/include/asm/unistd_64.h可以看64位Linux系统调用和系统调用号的对应关系。
Linux系统64位AT&T系统调用汇编指令syscall_第3张图片

man 2 exit可以看一下系统调用exit的相关信息,按q可以退出man界面。
Linux系统64位AT&T系统调用汇编指令syscall_第4张图片

系统调用使用到的寄存器:

寄存器 作用
rax 放入系统调用号
rdi 第1个参数
rsi 第2个参数
rdx 第3个参数
r10 第4个参数
r9 第5个参数
r8 第6个参数

除了系统调用号,内核还需要知道需要处理的参数,这就需要使用rdirsirdxr10r9r8等六个寄存器传递参数,系统调用最多只能传递6个参数。

返回值会放到rax寄存器里边,rcx在系统调用时会保存下一条指令位置,r11会保存eflags的数值。

示例

在C语言中使用系统调用,可以参考博客。

输出字符串

输出字符串使用到的寄存器和应该赋予的值:

寄存器
rax 1
rdi 文件描述符,要是想要输出到屏幕上,那么就需要把rdi赋值为1
rsi 输出字符串的地址
rdx 输出的字符个数

上边的表是系统调用之前进行放置的。
在系统调用完成之后rax会被放入返回值。
AT&T汇编代码displayStringATT64.s里边的代码如下:

.global main
.section .data
# 需要输出的字符串
stringToShow:
        .ascii "hello world\n\0"
.section .text
main:
        # 系统调用号,可以看一下/usr/include/asm/unistd_64.h里边功能和调用号对应关系
        movq $1,%rax
        # %rdi里边放的是系统调用write函数第一个参数,表示输出的位置,当rdi里边的数值是1,表明需要输出到标准输出
        movq $1,%rdi
        # %rsi里边放入的是系统调用write函数第二个参数,表示输出的内容
        movq $stringToShow,%rsi
        # %rsi里边放入的是系统调用write函数第三个参数,表示输出的内容长度,这里字符串的长度,包括“\n”,而不包括“\0”
        movq $12,%rdx
        syscall

       # 相当于C语言中的return 0
        movq $60,%rax
        movq $0,%rdi
        syscall

gcc displayStringATT64.s -o displayStringATT64把汇编代码进行编译,编译完成之后./displayStringATT64执行就会输出hello world
Linux系统64位AT&T系统调用汇编指令syscall_第5张图片
上边的汇编代码相当于下边的C语言stdoutputSimple.c代码:

#include 

int main()
{
    write(1,"hello world\n",12);
    return 0;
}

使用gcc stdoutputSimple.c -o stdoutputSimple进行编译,然后使用./stdoutputSimple执行输出hello world
在这里插入图片描述

输入字符串

输入字符串需要使用的寄存器及相应的赋值:

寄存器
rax 0
rdi 输入的位置,使用文件描述符进行表示,要是从标准输入读取的话,赋值为0
rsi 读取字符串的位置
rdx 读取的字符个数

从键盘上输入字符串到屏幕的AT&T汇编代码consoleInputATT.s里边的内容如下:

.section .data
        stringLength: .quad 5
        prompt:
            .ascii "Please input:"

.section .bss
        stringToFile: .skip 7
        oneChar: .skip 1

.section .text
.global main
    main:
        # rax = 1,输出的系统调用号
        movq $1,%rax
        movq $1,%rdi
        movq $prompt,%rsi
        movq $13,%rdx
        syscall

        # 此处取地址
        leaq oneChar,%rbx
        leaq stringToFile,%r10
        movq $0,%r12


    readCharacters:
        movq $0,%rax
        movq $0,%rdi
        movq %rbx,%rsi
        movq $1,%rdx
        syscall


        # 取值
        movq (%rbx),%rax
        cmpb $10,%al
        je printString

        incq %r12
        cmpq %r12,stringLength
        jb readCharacters

        movb %al,(%r10)
        incq %r10
        

        jmp readCharacters

    printString:
        incq %r10
        movb $10,(%r10)
        incq %r10
        movb $0,(%r10)
        movq $1,%rax
        movq $1,%rdi
        movq $stringToFile,%rsi
        movq %r12,%rdx
        syscall

        movq $60,%rax
        movq $0,%rdi
        syscall

gcc -g consoleInputATT.s -o consoleInputATT进行编译,./consoleInputATT执行,然后输入1234567,发现最后输出的是12345,符合预期,最多只能输入5个字符。
在这里插入图片描述

创建文件

在系统调用之前,需要放入值的寄存器和对应的值:

寄存器
rax 85
rdi 文件名称,需要以ASCII中的0(NULL)结束
rsi 文件访问权限

fileCreateATT.s里边的代码如下:

.global main
.section .data
    # 文件名称,”\0“是NULL的含义
    fileName:
        .ascii "fileText.txt\0"
.section .text
    # main函数
    main:
        # rax = 85,告诉内核需要创建文件
        movq $85,%rax
        # rdi = 文件名称,告诉内核创建文件的名称
        movq $fileName,%rdi
        # rsi = 文件访问权限,告诉内核文件是否有读、写、执行等权限
        movq $0600,%rsi
        syscall
        
	   # rax = 60,这是退出程序的系统调用号
        movq $60,%rax
        movq $0,%rdi
        syscall

gcc fileCreateATT.s -o fileCreateATT进行编译,ls -l fileText.txt若是显示ls: cannot access fileText.txt: No such file or directory,那么说明没有fileText.txt这个文件,./fileCreateATT进行执行,ls -l fileText.txt就可以显示文件的信息了,这说明创建成功了fileText.txt文件。
Linux系统64位AT&T系统调用汇编指令syscall_第6张图片

可以使用rm -rf fileText.txt删除已经创建的文件
在这里插入图片描述

创建文件并写入字符串

fileWriteATT.s里边的代码如下:

.global main
.section .data
    fileName:
        .ascii "writeToFile.txt\0"
    fileContextString:
        .ascii "good learn!\n\0"

.section .text
    main:
        movq $85,%rax
        movq $fileName,%rdi
        movq $0600,%rsi
        syscall

        # rax在系统调用之后就保存文件描述符,这里把文件描述符从rax中保存到rdi中
        movq %rax,%rdi
        movq $1,%rax
        movq $fileContextString,%rsi
        movq $12,%rdx
        syscall

        movq $60,%rax
        movq $0,%rdi
        syscall

gcc fileWriteATT.s -o fileWriteATT进行编译,./fileWriteATT进行执行,cat writeToFile.txt查看写入writeToFile.txt文件里边的内容。
Linux系统64位AT&T系统调用汇编指令syscall_第7张图片

关闭文件

关闭一个文件需要使用到的寄存器及相应的赋值:

寄存器
rax 3
rdi 文件描述符

此处的例子跟底下的打开文件读取文件里边内容例子写在一起。

打开文件读取文件里边内容

打开一个文件需要使用到的寄存器及相应的赋值:

寄存器
rax 2
rdi 文件名称
rsi 文件访问权限

需要注意的是,在系统调用之后,rax里边就会放入文件描述符返回,之后可以通过这个文件描述符才可以对这个文件进行操作,比如读写。

fileReadATT.s里边的内容如下:

.section .data
        fileName:
            .ascii "writeToFile.txt\0"
        fileDescriptor: .quad 0
.section .bss
        stringFromFile: .skip 14
.global main
.section .text
    main:
        movq $2,%rax
        movq $fileName,%rdi
        movq $00,%rsi
        movq $0444, %rdx
        syscall

        cmpq $0,%rax
        jbe done
        movq %rax,fileDescriptor


        movq $0,%rax
        movq fileDescriptor,%rdi
        movq $stringFromFile,%rsi
        movq $11,%rdx
        syscall

        movq $stringFromFile,%rdi
        movb $'\n',12(%rdi)
        # movb $'\0',13(%rdi)
        


        movq $1,%rax
        movq $1,%rdi
        movq $stringFromFile,%rsi
        movq $13,%rdx
        syscall
        
        movq $3,%rax
        movq fileDescriptor,%rdi
        syscall
	done:
        movq $60,%rax
        movq $0,%rdi
        syscall

gcc -g fileReadATT.s -o fileReadATT进行编译,上边产生可执行文件./fileWriteATT先执行产生一个writeToFile.txt文件,cat writeToFile.txt可以看一下writeToFile.txt文件里边的内容,确保上边程序执行正确,./fileReadATT执行读取文件内容。
Linux系统64位AT&T系统调用汇编指令syscall_第8张图片

讲到系统调用的汇编语言书籍:

书籍 作者 章节 章节名 语言 汇编语言风格 汇编器
x64汇编语言:从新手到AVX专家 Jo Van Hoey 第20章 文件I/O 汉语 Intel nasm
Low-Level Programming: C, Assembly, and Program Execution on Intel 64 Architecture Igor Zhirkov 第6章 Interrupts and System Calls 英语 Intel nasm
Introduction to 64 Bit Intel Assembly Language Programming for Linux Ray Seyfarth 第12章 System calls 英语 Intel yasm
x86-64 Assembly Language Programming with Ubuntu Ed Jorgensen 第13章 System Services 英语 Intel yasm
Introduction to Computer Organization: An Under-the-Hood Look at Hardware and x86-64 Assembly Robert G. Plantz 第21章 Interrupts and Exceptions 英语 Intel gas
Learn to Program with Assembly: Foundational Learning for New Programmers Jonathan Bartlett 第10章 Making a System Calls 英语 AT&T gas

此文章为10月Day 24学习笔记,内容来源于极客时间《深入 C 语言和程序运行原理》。

你可能感兴趣的:(汇编语言,深入C语言和程序运行原理,linux,汇编)