汇编知识小结

arm汇编中 函数调用参数个数超过几个入栈

在ARM汇编中,函数调用时,参数通常被传递到函数的栈帧中。如果函数调用的参数个数超过了寄存器的数量,那么多余的参数将被压入栈中,然后通过访问栈帧的方式获取参数值。

在ARM32架构中,函数调用时,有4个寄存器(r0、r1、r2、r3)可用于传递参数。如果函数调用的参数个数超过了这4个寄存器的数量,多余的参数将被依次压入栈中,并按照从右至左的顺序入栈。

例如,如果有一个函数调用需要传递5个参数,那么前4个参数可以通过r0、r1、r2、r3寄存器传递,第5个参数将被压入栈中。以下是一个示例:

 

    mov r0, #1          ; 第1个参数:1
    mov r1, #2          ; 第2个参数:2
    mov r2, #3          ; 第3个参数:3
    mov r3, #4          ; 第4个参数:4
    push {r4}           ; 为第5个参数分配8个字节的空间,并将其压入栈中
    push {r5}
    mov r4, #5          ; 第5个参数:5
    mov r5, #6          ; 第6个参数:6
    bl my_func          ; 调用函数
    add sp, sp, #8      ; 将栈指针恢复到调用前的位置
 

在上面的示例中,我们首先使用r0至r3寄存器分别将前4个参数值存储下来,然后使用PUSH指令将栈指针sp向下移动8个字节,并将第5个参数值存储到栈中。接着,我们使用PUSH指令再次将栈指针向下移动8个字节,并将第6个参数值存储到栈中。最后,我们调用函数my_func,并通过ADD指令将栈指针恢复到调用前的位置。

在ARM64架构中,函数调用时,有8个寄存器(x0至x7)可用于传递参数。如果函数调用的参数个数超过了这8个寄存器的数量,多余的参数将被依次压入栈中,并按照从右至左的顺序入栈。实现方式与ARM32类似,唯一的区别是使用的寄存器数目不同。

arm汇编中 arm64 系统调用和arm 系统调用的区别

在ARM和ARM64架构下,系统调用的实现方式有所不同。ARM架构下的系统调用使用软中断(SWI)指令来触发,而ARM64架构下的系统调用则使用svc(Supervisor Call)指令来触发。

具体来说,ARM架构下的系统调用可以通过如下方式实现:

  1. 将系统调用号(syscall number)存储在r7寄存器中;
  2. 将系统调用参数存储在r0至r6寄存器中;
  3. 执行软中断(SWI)指令。

执行软中断指令时,会切换到特权模式,并跳转到预定义的中断处理程序(interrupt handler)。中断处理程序会根据r7寄存器中的系统调用号,调用相应的系统调用函数,并将r0至r6寄存器中的参数传递给系统调用函数。系统调用函数执行完毕后,会将返回值存储在r0寄存器中,并通过中断处理程序返回到用户模式。

而在ARM64架构下,系统调用实现方式略有不同:

  1. 将系统调用号(syscall number)存储在x8寄存器中;
  2. 将系统调用参数存储在x0至x7寄存器中;
  3. 执行svc(Supervisor Call)指令。

执行svc指令时,会触发一个异常(exception),并切换到特权模式。异常处理程序会根据x8寄存器中的系统调用号,调用相应的系统调用函数,并将x0至x7寄存器中的参数传递给系统调用函数。系统调用函数执行完毕后,会将返回值存储在x0寄存器中,并通过异常处理程序返回到用户模式。

总的来说,ARM64架构下的系统调用和ARM架构下的系统调用实现方式类似,但是使用了不同的指令(svc替代了SWI)。这是由于ARM64架构支持更多的寄存器和更长的指令集,使得在系统调用实现中可以有更多的灵活性和效率。

arm汇编中 arm64 系统调用例子

在ARM64架构下,系统调用可以使用svc(Supervisor Call)指令来触发。以下是一个简单的示例,演示如何使用svc指令进行系统调用:

 

// 该函数使用系统调用输出一个字符串
// 参数:
// - r0:字符串地址
// 返回值:
// - r0:成功返回0,失败返回错误号
print_string:
    adr x1, string       // 将字符串地址存储到x1寄存器中
    mov x0, #1           // 将文件描述符1(标准输出)存储到x0寄存器中
    mov x2, #14          // 将字符串长度存储到x2寄存器中
    mov x8, #64          // 将系统调用号64存储到x8寄存器中
    svc #0               // 调用系统调用
    mov x0, #0           // 将返回值存储到x0寄存器中
    ret

    .data                // 定义数据段
string:
    .ascii "Hello world!\n"

 

在上面的示例中,我们定义了一个名为print_string的函数,该函数使用系统调用输出一个字符串。在函数中,我们首先使用ADR指令将字符串的地址存储到x1寄存器中,然后将文件描述符1(标准输出)存储到x0寄存器中,将字符串长度存储到x2寄存器中,将系统调用号64存储到x8寄存器中。最后,我们使用SVC指令调用系统调用,并将返回值存储到x0寄存器中。在数据段中,我们定义了要输出的字符串。

注意,在ARM64架构下,系统调用号不同于ARM架构下的系统调用号。ARM64架构下的系统调用号通常是从0开始的,且每个系统调用都有一个唯一的编号。要调用一个系统调用,需要将对应的系统调用号存储到x8寄存器中,然后使用svc指令触发系统调用。

arm汇编中 arm32 系统调用例子

在ARM32架构下,系统调用通常使用软中断(SWI)指令来触发。以下是一个简单的示例,演示如何使用SWI指令进行系统调用:

 

// 该函数使用系统调用输出一个字符串
// 参数:
// - r0:字符串地址
// 返回值:
// - r0:成功返回0,失败返回错误号
print_string:
    adr r1, string       // 将字符串地址存储到r1寄存器中
    mov r0, #1           // 将文件描述符1(标准输出)存储到r0寄存器中
    mov r2, #14          // 将字符串长度存储到r2寄存器中
    swi 0x0              // 调用系统调用
    mov r0, #0           // 将返回值存储到r0寄存器中
    bx lr                // 返回

    .data                // 定义数据段
string:
    .ascii "Hello world!\n"

 

在上面的示例中,我们定义了一个名为print_string的函数,该函数使用系统调用输出一个字符串。在函数中,我们首先使用ADR指令将字符串的地址存储到r1寄存器中,然后将文件描述符1(标准输出)存储到r0寄存器中,将字符串长度存储到r2寄存器中。最后,我们使用SWI指令调用系统调用,并将返回值存储到r0寄存器中。在数据段中,我们定义了要输出的字符串。

在ARM32架构下,系统调用号通常是从0x0开始的,且每个系统调用都有一个唯一的编号。要调用一个系统调用,需要将对应的系统调用号存储到PC寄存器中,并使用SWI指令触发软中断。系统调用完成后,返回值通常会存储在r0寄存器中。

arm汇编 adr 指令 解析

ARM汇编中的ADR指令用于将一个符号的地址加载到寄存器中。该指令的语法为:

ADR Rd, symbol

其中,Rd是目标寄存器,symbol是一个符号,可以是标签、常量或者变量的名称。

当汇编器处理ADR指令时,它会计算出symbol的地址,并将该地址加载到Rd寄存器中。因此,可以在程序中使用ADR指令来获取某个变量或函数的地址,而不必手动计算它的地址。

例如,假设有一个存储在代码段中的字符串,可以使用ADR指令将其地址存储到某个寄存器中,然后使用LDR指令从该地址中加载字符串的内容。示例如下:

    .section .rodata        @ 定义只读数据段
msg:                        @ 定义字符串标签
    .asciz "Hello, World!"

    .section .text          @ 定义代码段
    .globl _start
_start:
    adr r0, msg             @ 将msg标签的地址存储到r0中
    bl puts                 @ 调用puts函数输出字符串
    mov r7, #1              @ 设置退出系统调用号
    mov r0, #0              @ 设置退出状态码
    svc 0                   @ 调用系统调用

在上面的示例中,我们使用ADR指令将msg标签的地址存储到r0中,然后调用puts函数输出字符串。由于msg标签定义在只读数据段中,因此不能直接使用LDR指令从它的地址中加载字符串内容,而必须通过ADR指令先将其地址存储到寄存器中。

adlr 汇编指令解释

ADLR是一种汇编指令,它是ADD指令的变体,用来将一个操作数与另一个操作数相加,然后将结果存储到指定的寄存器中,并且在执行完毕后将状态寄存器设置为小于、等于或大于的标志。其语法为:

ADLR Rdest, Rsrc1, Src2

其中,Rdest是目标寄存器,Rsrc1是源寄存器,Src2是立即数或者第二个源操作数。执行后,将Rsrc1和Src2相加,结果存储到Rdest中,并设置状态寄存器的标志位。

ADLR指令通常用于比较两个操作数的大小,例如,如果需要比较R1和R2的大小,则可以使用ADLR R3, R1, R2指令,执行后,R3的值将是R1+R2,同时状态寄存器的标志位将根据R1和R2的大小关系被设置为小于、等于或大于的标志。

ADRL指令(Address Relative Load)是ARM汇编中的一种伪指令(Pseudo-instruction),用于将一个标签的地址加载到寄存器中。ADRL指令在编译时被汇编器翻译成两条指令,分别是ADR指令和LDR指令。

ADRL指令的语法如下:

ADRL Rd, label 

其中,Rd为要存储地址的寄存器,label为要加载地址的标签。

ADRL指令实际上是将标签的地址加载到PC寄存器中,然后使用ADR指令将PC寄存器的值存储到目标寄存器中。由于加载的地址是相对于PC寄存器的偏移量,因此可以使用ADRL指令来加载程序代码中的相对地址。

以下是一个ADRL指令的示例,演示如何加载一个标签的地址到寄存器中:

ADRL r0, my_label ; 将my_label的地址加载到r0寄存器中 ... my_label: ... 

在上面的示例中,我们使用ADRL指令将my_label标签的地址加载到r0寄存器中。在标签后面的代码中,我们可以使用r0寄存器来访问my_label的地址。由于ADRL指令在编译时被翻译成两条指令,因此在执行时会有一定的性能损失,但这种损失通常可以忽略不计。

arm汇编中 ldr,str,ldm,stm,伪指令ldr 解释

在ARM汇编中,ldr和str是用来读写内存的指令,ldm和stm是用来读写寄存器的指令。而伪指令ldr则是用来定义数据并将其加载到寄存器中的指令。

  1. ldr指令:用于从内存中加载数据到寄存器中。其语法为:ldr Rd, [Rn, #offset],其中Rd是目标寄存器,Rn是源寄存器,offset是偏移量。例如,ldr r1, [r0, #4],表示从地址(r0+4)中加载数据到r1寄存器中。

  2. str指令:用于将寄存器中的数据存储到内存中。其语法为:str Rd, [Rn, #offset],其中Rd是源寄存器,Rn是目标寄存器,offset是偏移量。例如,str r1, [r0, #4],表示将r1寄存器中的数据存储到地址(r0+4)中。

  3. ldm指令:用于从内存中连续读取多个数据到寄存器中。其语法为:ldm Rn!, {Rd1-RdN},其中Rn是源寄存器,Rd1-RdN是目标寄存器列表。例如,ldm r0!, {r1-r4},表示从地址(r0)中开始,依次读取4个数据到r1-r4寄存器中,并自动更新r0的值。

  4. stm指令:用于将多个寄存器中的数据连续存储到内存中。其语法为:stm Rn!, {Rd1-RdN},其中Rn是目标寄存器,Rd1-RdN是源寄存器列表。例如,stm r0!, {r1-r4},表示将r1-r4寄存器中的数据依次存储到地址(r0)中,并自动更新r0的值。

  5. 伪指令ldr:用于定义数据并将其加载到寄存器中。其语法为:ldr Rd, =CONST,其中Rd是目标寄存器,CONST是常量值。例如,ldr r0, =123,表示将常量123加载到r0寄存器中。在汇编时,伪指令ldr会被转换成多条真正的指令来完成加载操作。

arm64 naked类型 inline open实现

__attribute__((naked)) void my_open(const char *path, int flags) {
    __asm__ volatile (
        "mov x8, #2\n" // open系统调用号为2
        "svc #0\n"     // 调用系统调用
        "ret\n"        // 返回
    );
}

inline int open(const char *path, int flags) {
    int fd;
    my_open(path, flags);
    __asm__ volatile ("mov %0, x0" : "=r" (fd)); // 把返回值存储到fd中
    return fd;
}

该实现中,my_open函数使用naked属性表示该函数没有函数头和尾部,因此需要手动编写ARM汇编代码来实现系统调用。在该函数中,我们将系统调用号2放入x8寄存器中,并使用svc #0指令来调用系统调用。最后,我们使用ret指令来返回到调用者。

open函数中,我们首先调用my_open函数来执行系统调用,然后使用mov指令将返回值存储到fd变量中,并将其返回。由于open函数是一个inline函数,编译器会将其内联到调用代码中,这样就可以实现快速调用系统调用的效果。

以下是一个更完整的arm64 naked类型 inline open实现:

#include <fcntl.h>
#include <unistd.h>

__attribute__((naked)) int my_open(const char *pathname, int flags, mode_t mode) {
    __asm__(
        "mov x8, #2\n"  // 2代表open系统调用号
        "svc #0"        // 0代表系统默认的调用方式
        "ret"
    );
}

inline int open(const char *pathname, int flags, mode_t mode) {
    int fd;
    __asm__ volatile(
        "mov x0, %[pathname]\n"
        "mov x1, %[flags]\n"
        "mov x2, %[mode]\n"
        "bl my_open\n"
        "mov %[fd], x0\n"
        : [fd] "=r" (fd)
        : [pathname] "r" (pathname), [flags] "r" (flags), [mode] "r" (mode)
        : "x0", "x1", "x2"
    );
    return fd;
}
 

my_open函数中,我们使用naked属性来声明该函数没有函数头和尾部,因此需要手动编写ARM汇编代码来实现系统调用。在该函数中,我们将系统调用号2放入x8寄存器中,并使用svc #0指令来调用系统调用。最后,我们使用ret指令来返回到调用者。

open函数中,我们使用asm关键字声明内嵌汇编代码,使用mov指令将函数参数加载到寄存器中,然后使用bl指令调用my_open函数,将返回值存储到fd变量中,并将其返回。由于open函数是一个inline函数,编译器会将其内联到调用代码中,这样就可以实现快速调用系统调用的效果。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值