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


    相关概念

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

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

    系统调用一般函数(或者说用户函数)的最大区别在于,系统调用执行的代码位于操作系统底层的内核环境(内核环境也称作内核空间或应用程序的内核态,处于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系统调用和系统调用号的对应关系。
    在这里插入图片描述

    man 2 exit可以看一下系统调用exit的相关信息,按q可以退出man界面。
    在这里插入图片描述

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

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

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

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

    示例

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

    退出程序

    关于c语言中return与exit的差异,可以参考博客《C语言中的exit()函数》
    输出字符串使用到的寄存器和应该赋予的值:

    寄存器
    rax60
    rdi返回给操作系统的值

    testExit.s里边的代码如下:

    .global main
    .section .data
    .section .text
    main:
            # 下边这三条语句相当于C语言在main方法中使用return 0语句,或者在任意函数中使用exit(0)语句
            #  rax = 60,这是程序退出的系统调用
            movq $60,%rax
            #  告诉操作系统返回值为0
            movq $0,%rdi
            syscall
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    gcc testExit.s -o testExit进行编译,./testExit执行程序,echo $?可以查看最近一个程序返回给操作系统的值。
    在这里插入图片描述

    CPU写入字符串到标准输出(输出字符串到屏幕)

    输出字符串(从屏幕的角度看)使用到的寄存器和应该赋予的值:

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

    上边的表是系统调用之前进行放置的。
    在系统调用完成之后rax会被放入返回值。
    注意:在CPU的角度,这里的输出字符串相当于CPU写入其他地方,数据流出入方向是rsi->rdi
    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
            # rdx里边放入的是系统调用write函数第三个参数,表示输出的内容长度,这里字符串的长度,包括“\n”,而不包括“\0
            movq $12,%rdx
            syscall
    
            # 下边这三条语句相当于C语言在main方法中使用return 0语句,或者在任意函数中使用exit(0)语句
            #  rax = 60,这是程序退出的系统调用
            movq $60,%rax
            #  告诉操作系统返回值为0
            movq $0,%rdi
            syscall
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    gcc displayStringATT64.s -o displayStringATT64把汇编代码进行编译,编译完成之后./displayStringATT64执行就会输出hello world
    在这里插入图片描述
    上边的汇编代码相当于下边的C语言stdoutputSimple.c代码:

    #include 
    
    int main()
    {
        write(1,"hello world\n",12);
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    CPU读取字符串到内存(从键盘输入字符串)

    输入字符(这里是从键盘角度理解)需要使用的寄存器及相应的赋值:

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

    注意:在CPU的角度,这里的输入字符串相当于CPU读取内容,数据流出入方向是rdi->rsi
    从键盘上输入字符串到屏幕的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
            # rdi = 1,表示需要输出到标准输出
            movq $1,%rdi
            # rsi = 字符串的地址,表明需要输出的内容
            movq $prompt,%rsi
            # rdx = 13,表明需要向屏幕输出的字符个数——13
            movq $13,%rdx
            syscall
    
            # 此处取oneChar地址到rbx中
            leaq oneChar,%rbx
            # 此处取stringToFile地址到r10中
            leaq stringToFile,%r10
            # r12里边放的是字符的个数,用来限制最后存入的字符长度
            movq $0,%r12
    
        # CPU开始从键盘读取字符串到内存
        readCharacters:
            # rax = 00是输入的系统调用号,表明CPU开始读取字符串
            movq $0,%rax
            # rdi = 0,代表标准输入,标准输入一般代表键盘
            movq $0,%rdi
            # rbx里边存放的是oneChar地址,表明需要CPU输出的方向是oneChar所在的内存
            movq %rbx,%rsi
            # rdx = 1,这里是录入的字符个数,说明这里只录入一个字符
            movq $1,%rdx
            syscall
    
    
            # 把rbx代表的oneChar地址里边的值(刚刚输入的字符)取出来,放到rax里边
            movq (%rbx),%rax
            # 10是ASCII回车字符,要是输入的是回车的话,就跳转到输出字符串地址
            cmpb $10,%al
            je printString
    
            # r12 = r12 + 1
            incq %r12
            # stringLength < r12,会接着输入字符,但是不会把输入的字符放到最后输出的字符串(stringToFile)里边。
            cmpq %r12,stringLength
            jb readCharacters
            
            # stringLength >= r12,才会往下进行执行
            # 把字符串放到输出的字符串(stringToFile)里边
            movb %al,(%r10)
            # 把存放字符的地址往后挪一位
            incq %r10
            
            # 接着进行读取字符,这样的话,可以避免缓存区溢出
            jmp readCharacters
    
        printString:
            # 更新一下存放字符的位置
            incq %r10
            # 把倒数第二字符变成回车符
            movb $10,(%r10)
            # 更新一下存放字符的位置
            incq %r10
            # 把最后一个字符串复制为NULL
            movb $0,(%r10)
            # rax = 1,表明这是输出系统调用
            movq $1,%rax
            # rdi = 1,标准输出
            movq $1,%rdi
            # rsi里边存放输出的内容
            movq $stringToFile,%rsi
            # rdx = r12,说明最后输入r12的字符串
            movq %r12,%rdx
            syscall
    
            # 下边这三条语句相当于C语言在main方法中使用return 0语句,或者在任意函数中使用exit(0)语句
            #  rax = 60,这是程序退出的系统调用
            movq $60,%rax
            #  告诉操作系统返回值为0
            movq $0,%rdi
            syscall
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88

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

    创建文件

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

    寄存器
    rax85
    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
    
            # 下边这三条语句相当于C语言在main方法中使用return 0语句,或者在任意函数中使用exit(0)语句
            #  rax = 60,这是程序退出的系统调用号
            movq $60,%rax
            #  告诉操作系统返回值为0
            movq $0,%rdi
            syscall
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    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文件。
    在这里插入图片描述

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

    创建文件并写入字符串

    fileWriteATT.s里边的代码如下:

    .global main
    .section .data
        fileName:
            .ascii "writeToFile.txt\0"
        fileContextString:
            .ascii "good learn!\n\0"
    
    .section .text
        main:      
            # rax = 85,告诉内核需要创建文件
            movq $85,%rax
            # rdi里边存放创建文件的名称
            movq $fileName,%rdi
            # rsi放入文件的权限
            movq $0600,%rsi
            syscall
    
            # rax在系统调用之后就保存文件描述符,这里把文件描述符从rax中保存到rdi中
            movq %rax,%rdi
            # rax = 1,告诉操作系统需要写入文件
            movq $1,%rax
            # rsi保存需要写入到文件的字符串
            movq $fileContextString,%rsi
            # rdx里边是写入文件的字符串长度
            movq $12,%rdx
            syscall
    
            # 下边这三条语句相当于C语言在main方法中使用return 0语句,或者在任意函数中使用exit(0)语句
            #  rax = 60,这是程序退出的系统调用号
            movq $60,%rax
            #  告诉操作系统返回值为0
            movq $0,%rdi
            syscall
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    gcc fileWriteATT.s -o fileWriteATT进行编译,./fileWriteATT进行执行,cat writeToFile.txt查看写入writeToFile.txt文件里边的内容。
    在这里插入图片描述

    关闭文件

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

    寄存器
    rax3
    rdi文件描述符

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

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

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

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

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

    fileReadATT.s可以把文件里边的内容输入到屏幕上,代码如下:

    .section .data
            fileName:
                .ascii "writeToFile.txt\0"
            # fileDescriptor是一个8字节的整数
            fileDescriptor: .quad 0
    .section .bss
            stringFromFile: .skip 14
    .global main
    .section .text
        main:
            # rax = 2,这是打开文件的系统调用号
            movq $2,%rax
            # rdi需要打开文件名
            movq $fileName,%rdi
            # rsi里边存放着文件操作权限
            movq $00,%rsi
            syscall
            
            # rax里边放着打开文件系统调用的返回值,如果rax<=0,那么就需要退出程序。
            cmpq $0,%rax
            jbe done
            # rax>0,那么rax里边存放的就是上边调用打开文件系统调用的文件描述符,fileDescriptor里边存放这个字符串
            movq %rax,fileDescriptor
            
            # rax = 0,CPU读取的系统调用号,表明CPU从文件里边读取内容放入到内存里边,数据流方向:rdi->rsi
            movq $0,%rax
            # rdi里边存放文件描述符,表明需要CPU读取内容的文件
            movq fileDescriptor,%rdi
            # rsi表示内容放置的内存位置
            movq $stringFromFile,%rsi
            # rdx存放内容的长度
            movq $11,%rdx
            syscall
            
            # rdi里边放入的stringFromFile里边的地址
            movq $stringFromFile,%rdi
            # 在stringFromFile字符串最后放入一个换行符
            movb $'\n',12(%rdi)
            # movb $'\0',13(%rdi)
          
            # rax = 1,CPU写入的系统调用,数据流方向:rsi->rdi
            movq $1,%rax
            # rdi里边是CPU写入的方向
            movq $1,%rdi
            # rsi是CPU读取的内存位置
            movq $stringFromFile,%rsi
            # rdx是CPU写入的长度
            movq $13,%rdx
            syscall
            
            # rax = 3,CPU关闭文件的系统调用号
            movq $3,%rax
            # rdi里边是文件描述符
            movq fileDescriptor,%rdi
            syscall
    	done:
             # 下边这三条语句相当于C语言在main方法中使用return 0语句,或者在任意函数中使用exit(0)语句
            #  rax = 60,这是程序退出的系统调用号
            movq $60,%rax
            #  告诉操作系统返回值为0
            movq $0,%rdi
            syscall
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    gcc -g fileReadATT.s -o fileReadATT进行编译,上边产生可执行文件./fileWriteATT先执行产生一个writeToFile.txt文件,cat writeToFile.txt可以看一下writeToFile.txt文件里边的内容,确保上边程序执行正确,./fileReadATT执行读取文件内容。
    在这里插入图片描述

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

    书籍作者章节章节名语言汇编语言风格汇编器
    x64汇编语言:从新手到AVX专家Jo Van Hoey第20章文件I/O汉语Intelnasm
    Low-Level Programming: C, Assembly, and Program Execution on Intel 64 ArchitectureIgor Zhirkov第6章Interrupts and System Calls英语Intelnasm
    Introduction to 64 Bit Intel Assembly Language Programming for LinuxRay Seyfarth第12章System calls英语Intelyasm
    x86-64 Assembly Language Programming with UbuntuEd Jorgensen第13章System Services英语Intelyasm
    Introduction to Computer Organization: An Under-the-Hood Look at Hardware and x86-64 AssemblyRobert G. Plantz第21章Interrupts and Exceptions英语Intelgas
    Learn to Program with Assembly: Foundational Learning for New ProgrammersJonathan Bartlett第10章Making a System Calls英语AT&Tgas
  • 相关阅读:
    SpringBoot项目--如何不停服更新应用?
    shell基本系统维护命令
    SpringBoot底层注解总结
    [附源码]java毕业设计基于JavaEE的机票预定系统
    微信小程序:炫酷恶趣制作神器小程序源码
    golang结构与接口方法实现与交互使用示例
    C++指针(五)完结篇
    如何将Linux上部署的5.7MySql数据库编码修改utf8(最新版)
    Luatos Air700 改变BL0942串口波特率
    硬盘接口随机
  • 原文地址:https://blog.csdn.net/qq_42108074/article/details/132804264