下面的代码包含一些内联汇编模板:
static inline uintptr_t arch_syscall_invoke3(uintptr_t arg1, uintptr_t arg2,
uintptr_t arg3,
uintptr_t call_id)
{
register uint32_t ret __asm__("r0") = arg1;
register uint32_t r1 __asm__("r1") = arg2;
register uint32_t r2 __asm__("r2") = arg3;
register uint32_t r6 __asm__("r6") = call_id;
__asm__ volatile("svc %[svid]n"
: "=r"(ret), "=r"(r1), "=r"(r2) <===================== HERE 1
: [svid] "i" (_SVC_CALL_SYSTEM_CALL), <===================== HERE 2
"r" (ret), "r" (r1), "r" (r2), "r" (r6)
: "r8", "memory", "r3", "ip");
return ret;
}
我和https://godbolt.org/z/znMeEMrEz像这样:
push {r6, r7, r8} ------------- A -------------
sub sp, sp, #20
add r7, sp, #0
str r0, [r7, #12]
str r1, [r7, #8]
str r2, [r7, #4]
str r3, [r7]
ldr r0, [r7, #12]
ldr r1, [r7, #8]
ldr r2, [r7, #4]
ldr r6, [r7]
svc #3 ------------- B -------------
mov r3, r0 ------------- C1 -------------
mov r0, r3 ------------- C2 -------------
adds r7, r7, #20
mov sp, r7
pop {r6, r7, r8}
bx lr
从A
到B
,汇编代码只是确保输入参数存在于目标寄存器中。我想这是某种系统调用约定。
我不明白HERE 1
和HERE 2
的目的。
问题1:
根据这里,HERE 1
应该是OutputOperands部分,这意味着
由汇编模板中的指令修改的C变量的逗号分隔列表
这是否意味着特定请求的系统调用函数将修改ret/r0
、r1
和r2
注册表?
问题2:
对于HERE 2
,它意味着InputOperands,这意味着:
汇编模板中的指令读取的C表达式的逗号分隔列表。允许使用空列表。请参见InputOperands
根据此处,SVC
指令只需要1个参数imm
。但我们指定了4个输入操作数,如ret
、r1
、r2
、r6
。
为什么我们需要指定这么多呢?
我想这些寄存器是svc处理程序使用的,所以我需要在SVC
指令之前准备它们。但是,如果我只是像从A
到B
那样准备它们,并且不将它们作为输入操作数,该怎么办?会有错误吗?
问题3:
最后,C1
和C2
有什么意义?它们看起来完全多余。r0
仍然存在。
我想这是某种系统调用约定。
这是未经优化的编译结果。仔细观察代码中发生的事情,可以发现在保存r6、r7和r8之后,它所做的只是将r3移动到r6,其他一切都是多余的。
问题1:这是否意味着特定请求的系统调用函数将修改ret/r0、r1和r2注册表?
是。
问题2:根据此处,SVC指令只需要1个参数imm。但我们指定了4个输入操作数,如ret、r、r2、r6。
我们指定imm以生成正确的SVC指令,并指定其余指令以确保我们调用的系统调用将在系统调用ABI中记录的寄存器中找到其参数。
为什么我们需要指定这么多?
根据函数名,它是一个3参数的系统调用,因此我们有3个系统调用参数,显然还有系统调用标识符。
但是,如果我只是像从A到B那样准备它们,而不将它们作为输入操作数,该怎么办?会有错误吗?
如果不在asm语句中提及它们作为输入,就无法可靠地执行just prepare them like from A to B
部分。仅仅将函数参数分配给局部变量是不够的,因为没有什么能强制执行此赋值和asm语句的正确顺序。除非编译时将警告作为错误,并且已为未使用但已设置的变量启用警告,否则不会出现编译时错误。
问题3:最后,C1和C2的意义是什么?它们看起来完全多余。r0仍然存在。
确实如此。使用-O进行编译将消除这种多余的移动以及大部分序言。