Android Arm64系统调用实现

  • Post author:
  • Post category:其他


本文主要基于clone系统调用分析在Arm64中代码流如何从用户态进入内核态,如何从内核态返回用户态,以及如何实现一次调用两次返回。

Arm64总共有4个异常级别,这里主要讨论EL0和EL1这两个异常级别。当程序运行在用户态时是EL0,当程序运行在内核态时一般是EL1. 寄存器有两种,一种是普通寄存器,一种是特殊寄存器。汇编代码种常用的x0、x1等就是普通寄存器。而栈指针寄存器、程序状态寄存器、异常连接寄存器等就是特殊寄存器。在EL0级别下栈指针寄存器是SP_EL0,在EL1级别下就是SP_EL1,当在不同的异常级别下切换时,SP就代表SP_EL0或者SP_EL1. 当然在EL1级别下也能访问到SP_EL0,但在EL0下无法访问SP_EL1。程序状态寄存器SPSR_EL1保存从EL0转到EL1级别时的状态寄存器。ELR_EL1异常连接寄存器保存EL0转到EL1级别时异常代码也就是PC的位置。由于不会有发生异常时将cpu核心的状态转到EL0级别(只会有处理完异常后返回EL0级别),所以没有SPSR_EL0和ELR_EL0。汇编指令svc是用于从EL0转到EL1异常级别。

ARM64异常级别的一些介绍

另外,ELR_EL1保存的是哪一个指令的位置呢,是产生异常的指令还是产生异常的下一个指令?当一个异常是由专门异常生成指令产生的时候,比如svc指令,它是专门用来生成一个异常然后从EL0切换到EL1的,ELR_EL1保存的就是svc指令的下一条指令位置。当一个异常是同步异常但不是由专门的生成异常指令触发的时候,ELR_EL1保存的是产生异常的那个指令位置,比如一个指针访问了一个没有映射过的地址,mmu找不到对应的页表而产生了一个异常,这时ELR_EL1保存的是访问这个指针的指令的地址。

下面通过代码一步步分析具体实现。

// pid_t __bionic_clone(int flags, void* child_stack, pid_t* parent_tid, void* tls, pid_t* child_tid, int (*fn)(void*), void* arg);

ENTRY_PRIVATE(__bionic_clone)
    # Push 'fn' and 'arg' onto the child stack.
    stp     x5, x6, [x1, #-16]!

    # Make the system call.
    mov     x8, __NR_clone //将对应的系统调用号保存到x8
    svc     #0 //转到EL1异常级别,PC保存到ELR_EL1中,程序状态如零标志位,溢出标志位等保存在SPSR_EL1中。

    # Are we the child?
    cbz     x0, .L_bc_child

    # Set errno if something went wrong.
    cmn     x0, #(MAX_ERRNO + 1)
    cneg    x0, x0, hi
    b.hi    __set_errno_internal

    ret

.L_bc_child:
    # We're in the child now. Set the end of the frame record chain.
    mov     x29, #0
    # Setting x30 to 0 will make the unwinder stop at __start_thread.
    mov     x30, #0
    # Call __start_thread with the 'fn' and 'arg' we stored on the child stack.
    ldp     x0, x1, [sp], #16
    b       __start_thread
END(__bionic_clone)

clone的调用过程,首先将新线程的执行方法



版权声明:本文为qq_16374771原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。