c-gcc arm在系统调用之前优化客场参数



我正在尝试使用gcc-arm在arm7tdmi-s上实现一些"OSEK服务"。不幸的是,提高优化级别会导致"错误"的代码生成。我不明白的主要一点是,编译器似乎忽略了过程调用标准,例如,通过将参数移动到寄存器r0-r3来将参数传递给函数。我知道函数调用可以内联,但参数仍然需要在寄存器中才能执行系统调用。

考虑以下代码来演示我的问题:

unsigned SysCall(unsigned param)
{
volatile unsigned ret_val;
__asm __volatile
(
"swi 0          nt"    /* perform SystemCall */
"mov %[v], r0   nt"    /* move the result into ret_val */
: [v]"=r"(ret_val) 
:: "r0" 
);
return ret_val;              /* return the result */
}
int main()
{
unsigned retCode;
retCode = SysCall(5); // expect retCode to be 6 when returning back to usermode
}

我在汇编中编写了顶级软件中断处理程序如下:

.type   SWIHandler, %function
.global SWIHandler
SWIHandler:
stmfd   sp! , {r0-r2, lr}        @save regs
ldr     r0  , [lr, #-4]          @load sysCall instruction and extract sysCall number
bic     r0  , #0xff000000
ldr     r3  , =DispatchTable     @load dispatchTable 
ldr     r3  , [r3, r0, LSL #2]   @load sysCall address into r3 
ldmia   sp, {r0-r2}              @load parameters into r0-r2
mov     lr, pc
bx      r3 
stmia   sp ,{r0-r2}              @store the result back on the stack
ldr     lr, [sp, #12]            @restore return address
ldmfd   sp! , {r0-r2, lr}        @load result into register
movs    pc  , lr                 @back to next instruction after swi 0

调度表如下所示:

DispatchTable:
.word activateTaskService
.word getTaskStateService

SystemCall函数如下所示:

unsigned activateTaskService(unsigned tID)
{
return tID + 1; /* only for demonstration */
}

在没有优化的情况下运行,一切都很好,并且参数按预期在寄存器中:请参阅以下代码-O0优化:

00000424 <main>:
424:   e92d4800    push    {fp, lr}
428:   e28db004    add fp, sp, #4
42c:   e24dd008    sub sp, sp, #8
430:   e3a00005    mov r0, #5          @move param into r0
434:   ebffffe1    bl  3c0 <SysCall>
000003c0 <SysCall>:
3c0:   e52db004    push    {fp}        ; (str fp, [sp, #-4]!)
3c4:   e28db000    add fp, sp, #0
3c8:   e24dd014    sub sp, sp, #20
3cc:   e50b0010    str r0, [fp, #-16]
3d0:   ef000000    svc 0x00000000
3d4:   e1a02000    mov r2, r0
3d8:   e50b2008    str r2, [fp, #-8]
3dc:   e51b3008    ldr r3, [fp, #-8]
3e0:   e1a00003    mov r0, r3
3e4:   e24bd000    sub sp, fp, #0
3e8:   e49db004    pop {fp}        ; (ldr fp, [sp], #4)
3ec:   e12fff1e    bx  lr

使用-O3编译相同的代码会产生以下汇编代码:

00000778 <main>:
778:   e24dd008    sub sp, sp, #8
77c:   ef000000    svc 0x00000000         @Inline SystemCall without passing params into r0
780:   e1a02000    mov r2, r0
784:   e3a00000    mov r0, #0
788:   e58d2004    str r2, [sp, #4]
78c:   e59d3004    ldr r3, [sp, #4]
790:   e28dd008    add sp, sp, #8
794:   e12fff1e    bx  lr

请注意systemCall是如何在不分配值5 t0 r0的情况下内联的。

我的第一种方法是通过调整上面的函数SysCall,手动将这些值移动到寄存器中,如下所示:

unsigned SysCall(volatile unsigned p1)
{
volatile unsigned ret_val;
__asm __volatile
(
"mov r0, %[p1]      nt"
"swi 0              nt"
"mov %[v], r0       nt" 
: [v]"=r"(ret_val) 
: [p1]"r"(p1)
: "r0"
);
return ret_val;
}

它似乎在这个最小的例子中起作用,但我不太确定这是否是最好的做法。为什么编译器认为在内联函数时可以省略参数?有人对这种方法是否可行或应该采取什么不同的做法有什么建议吗?

提前感谢

C源代码中的函数调用不会指示编译器根据ABI调用函数。它指示编译器根据C标准中的模型调用函数,这意味着编译器必须以其选择的方式将参数传递给函数,并以具有与C标准中定义的相同可观察效果的方式执行函数。

这些可观察的效果不包括设置任何处理器寄存器。当C编译器内联函数时,不需要设置任何特定的处理器寄存器。如果它使用ABI对外部调用调用函数,那么它将不得不设置寄存器。内联调用不需要遵守ABI。

因此,仅仅将系统请求放入由C源代码构建的函数中并不能保证设置任何寄存器。

对于ARM,您应该定义分配给所需寄存器的寄存器变量,并将其用作汇编指令的输入和输出:

unsigned SysCall(unsigned param)
{
register unsigned Parameter __asm__("r0") = param;
register unsigned Result    __asm__("r0");
__asm__ volatile
(
"swi 0"
: "=r" (Result)
: "r"  (Parameter)
: // "memory"    // if any inputs are pointers
);
return Result;
}

(这是GCC的一个大杂烩;它很难看,文档也很差https://stackoverflow.com/tags/inline-assembly/info对于某些链接。某些ISAs的GCC具有方便的特定寄存器约束,您可以使用它来代替r,但不能用于ARM。(寄存器变量不需要是易失性的;编译器知道它们将被用作汇编指令的输入和输出。

如果asm语句除了产生返回值之外还有其他副作用,那么它本身应该是volatile。(例如,getpid()不需要是volatile。(

如果输出未被使用,则具有输出的非volatileasm语句可以被优化掉,或者如果它与同一输入一起使用(如纯函数调用(,则可以从循环中提升出来。这几乎从来都不是系统调用所需要的。

如果任何输入都是指向内核将读取或修改的内存的指针,那么您还需要一个"memory"clobber。请参阅如何指示可以使用内联ASM参数指向的内存*?了解更多细节(以及使用伪存储器输入或输出以避免"memory"阻塞的方法。(

对mmap/munmap或其他影响内存含义的系统调用使用"memory"clobber也是明智的;您不希望编译器决定在munmap之后而不是之前进行存储。

最新更新