ARM64上内联汇编实现系统调用

ARM64上内联汇编实现系统调用

系统调用约定

  • 系统调用号放在 x8 寄存器中
  • 参数放在 x0x5 寄存器中
  • 系统调用的返回值放在 x0 寄存器中
#include <stdio.h>
#include <stdint.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <syscall.h>


int svc_openat(int dir_fd, const char *filename, int flags, mode_t mode)
{
    int result;
    // ARM64 assembly code to invoke the openat syscall
    asm volatile(
        "mov x8, %1\n"          // syscall number for openat (56 on ARM64) 
        "mov x0, %2\n"          // dirfd (directory file descriptor)
        "mov x1, %3\n"          // pathname (pointer to the string)
        "mov x2, %4\n"          // flags
        "mov x3, %5\n"          // mode
        "svc #0\n"              // Call the syscall
        "mov %0, x0\n"          // Store the result in 'result'
        : "=r" (result)         // 输出操作数列表
        : "i" (__NR_openat), "r" (dir_fd), "r" (filename), "r" (flags), "r" (mode)   //输入操作数列表,对应占位符顺序
        : "x0", "x1", "x2", "x3", "x8"    // 告诉编译器在这段汇编代码中会被修改的寄存器, 顺序可以改变
    );
    return result;
}

int main() {
    int dir_fd = AT_FDCWD;  // 使用当前工作目录作为目录文件描述符
    const char *filename = "example.txt";
    int flags = O_RDONLY;  // 您可以设置适当的标志
    mode_t mode = 0;      // 您可以设置适当的文件模式

	int result = svc_openat(dir_fd, filename, flags, mode);
    printf("result code %d\n", result);
    if (result >= 0) {
        printf("File opened successfully. File descriptor: %d\n", result);
        close(result);
    } else {
        perror("Error opening file");
    }

    return 0;
}

在上面的代码中,%1%2%3 等是内联汇编中的占位符,用于表示输入或输出操作数。这些占位符与C代码中的变量相对应,用于指定要传递给汇编代码的参数。

具体来说,在以下的代码行中:

%1 表示 __NR_openat,它是 openat 系统调用的系统调用号。"i" (__NR_openat) 中的 "i" 表示立即数(immediate),这里用于表示系统调用号是一个常数值。

%2 表示 dir_fd,它是目录文件描述符,表示在哪个目录中打开文件。在这里,"r" (dir_fd) 中的 "r" 表示寄存器(register),用于表示 dir_fd 是一个寄存器传递的参数。

%3 表示 filename,它是要打开的文件名字符串的指针。在这里,"r" (filename) 中的 "r" 同样表示寄存器传递的参数。

%4 和 %5 分别表示 flags 和 mode,它们是打开文件的标志和权限模式。同样,"r" 表示这些参数是通过寄存器传递的。

最后的 : "=r" (result) 表示将汇编代码中的结果存储到 result 变量中,并告诉编译器使用哪些寄存器作为输入和输出,以及哪些寄存器的值可能会在汇编代码块中被修改。

这种使用占位符的方式使得您可以将C代码中的变量传递给内联汇编,并在汇编代码中使用它们,同时告诉编译器如何处理这些参数。


GDB观测运行状态

查看汇编代码gcc -S svc.c

svc_openat:
.LFB6:
    .cfi_startproc
    sub sp, sp, #48        // 开辟栈空间
    .cfi_def_cfa_offset 48
    str w0, [sp, 28]       // w0加载到sp+28的地址上
    str x1, [sp, 16]       // x1加载到sp+16的地址上
    str w2, [sp, 24]
    str w3, [sp, 12]
    ldr w4, [sp, 28]
    ldr x5, [sp, 16]
    ldr w6, [sp, 24]
    ldr w7, [sp, 12]
#APP
// 15 "svc.c" 1
    mov x8, 56        // 可以看出__NR_openat系统调用号为56
mov x0, x4
mov x1, x5
mov x2, x6
mov x3, x7
svc #0
mov x4, x0

// 0 "" 2
#NO_APP
    str w4, [sp, 44]
    ldr w0, [sp, 44]
    add sp, sp, 48
    .cfi_def_cfa_offset 0
    ret
    .cfi_endproc
.LFE6:
    .size   svc_openat, .-svc_openat

理解图

 高地址(High Address)
+------------------+
|                  | <- sp (初始)
+------------------+
|        ...       |
|                  |
+------------------+
|       w3         | <- sp + 12
+------------------+
|       x1         |
+------------------+
|       w2         | <- sp + 24
+------------------+
|       w0 (-> w4) | <- sp + 28
+------------------+
|                  |
+------------------+
|                  | <- sp + 48
+------------------+

  低地址(Low Address)

str w0, [sp, 28]:将 w0 的值存储到 sp + 28
ldr w4, [sp, 28]:从 sp + 28 位置加载值到 w4


使用GDB验证上面gcc -g svc.s过程

(gdb) b svc_openat
Breakpoint 1 at 0x7f4: file svc.s, line 10.
(gdb) r
Starting program: /data/workspace/a.out 

Breakpoint 1, svc_openat () at svc.s:10
10		sub	sp, sp, #48
(gdb) si
12		str	w0, [sp, 28]
(gdb) si
13		str	x1, [sp, 16]
(gdb) 
....
(gdb) si
19		ldr	w7, [sp, 12] 
(gdb) si     // 单步到22,此时mov指令还没执行, 刚执行完上一条ldr
22		mov x8, 56

根据汇编代码mov x1, x5和系统调用约定,我们知道
x0是存放dir_fd
x1是存放filename
以此类推

mov x1, x5因为x1来自于x5,因此我们直接打印x5寄存器的值,应该是"example.txt",同时ldr x5, [sp, 16]表示x5是从sp+16加载到寄存器的,因此sp+16的地址应该就是字符串地址

以此类推,mov x0, x4因为x0来自于x4,且ldr w4, [sp, 28],因此sp+28存放的就是dir_fd的值为-100

(gdb) x/s $x5
0x5555555970:	"example.txt"
(gdb) x/8x $sp+16
0x7ffffffa60:	0x70	0x59	0x55	0x55	0x55	0x00	0x00	0x00
(gdb) x/s 0x5555555970
0x5555555970:	"example.txt"
(gdb) x/d $sp+28
0x7ffffffa6c:	-100

SVC内核调用栈

[  596.333596] Hardware name: Qualcomm Technologies, Inc. qrb5165 IOT RB5 (DT)
[  596.333597] Call trace:
[  596.333604]  dump_backtrace+0x0/0x250
[  596.333607]  show_stack+0x20/0x30
[  596.333611]  dump_stack+0xb8/0xf4
[  596.333614]  syscall_comm_entry_handler+0x23c/0x270 [ktracer]
[  596.333616]  faccessat_entry_handler+0x2c/0x40 [ktracer]
[  596.333619]  pre_handler_kretprobe+0x8c/0x160
[  596.333622]  kprobe_breakpoint_handler+0x164/0x210
[  596.333624]  brk_handler+0x44/0x170
[  596.333625]  do_debug_exception+0xac/0x138
[  596.333627]  el1_dbg+0x18/0x78
[  596.333629]  do_faccessat+0x0/0x308
[  596.333631]  el0_svc_common+0xa0/0x188
[  596.333633]  el0_svc_handler+0x6c/0x88
[  596.333634]  el0_svc+0x8/0xc

posted @ 2023-09-28 18:05  梦过无声  阅读(673)  评论(0)    收藏  举报