从内联汇编中正确收集返回值

问题描述 投票:0回答:1

我想使用inline assemblysyscall PowerPC架构上执行32-bit。执行syscall后,我还想通过取syscallr3的值并将它们放入r4来返回long的返回值。我的功能如下:

constexpr auto maximum_syscall_parameter_count = 8;

long execute_system_call_with_arguments(short value, const int parameters_array[maximum_syscall_parameter_count]) {    
    char return_value_buffer[sizeof(long)];

    // syscall value
    asm volatile("mr 0, %0" : : "r" (value));

    // Pass the parameters
    asm volatile("mr 3, %0" : : "r" (parameters_array[0]));
    asm volatile("mr 4, %0" : : "r" (parameters_array[1]));
    asm volatile("mr 5, %0" : : "r" (parameters_array[2]));
    asm volatile("mr 6, %0" : : "r" (parameters_array[3]));
    asm volatile("mr 7, %0" : : "r" (parameters_array[4]));
    asm volatile("mr 8, %0" : : "r" (parameters_array[5]));
    asm volatile("mr 9, %0" : : "r" (parameters_array[6]));
    asm volatile("mr 10, %0" : : "r" (parameters_array[7]));

    // Execute the syscall
    asm volatile ("sc");

    // Retrieve the return value
    asm volatile ("mr %0, 3" : "=r" (*(int *) &return_value_buffer));
    asm volatile ("mr %0, 4" : "=r" (*(int *) &return_value_buffer[sizeof(int)]));

    return *(long *) &return_value_buffer;
}

这似乎生成正确的代码,但是感觉很hacky,生成了2条冗余指令:

mr        r0, r30
lwz       r9, 0(r31)
mr        r3, r9
lwz       r9, 4(r31)
mr        r4, r9
lwz       r9, 8(r31)
mr        r5, r9
lwz       r9, 0xC(r31)
mr        r6, r9
lwz       r9, 0x10(r31)
mr        r7, r9
lwz       r9, 0x14(r31)
mr        r8, r9
lwz       r9, 0x18(r31)
mr        r9, r9
lwz       r9, 0x1C(r31)
mr        r10, r9
sc
mr        r3, r3 # Redundant
mr        r9, r4 # Redundant
blr

[我的目标是简单地使用r3指令设置的r4sc返回,但是从源代码中删除返回值或最后2个内联汇编指令将损坏该函数,使其在返回或返回时崩溃0

c++ assembly inline-assembly 32-bit powerpc
1个回答
2
投票

让我重新重申以上内容:我不会说PPC汇编语言,也没有PPC来运行此代码。因此,尽管我相信通常这是您应该遵循的方向,但不要将此代码当作福音。

接下来,Jester和我都建议使用local register variables的原因是它可以产生更好的代码(并且可以说更具可读性/可维护性)。原因是gcc docs

中的这一行

GCC不会自行解析汇编程序指令,也不知道它们的含义,甚至不知道它们是否是有效的汇编程序输入。

牢记这一点,当您使用上面的代码并使用如下代码调用例程时会发生什么:

int parameters_array[maximum_syscall_parameter_count] = {1, 2, 3, 4, 5, 6, 7};

long a = execute_system_call_with_arguments(9, parameters_array);

由于编译器不知道该asm块内部将发生什么,因此它[[必须将所有内容都写入内存,然后asm块将从内存中读取回寄存器。在使用如下代码时,编译器可以非常聪明地跳过分配内存并直接加载寄存器的过程。如果使用(基本上)相同的参数多次调用execute_system_call_with_arguments,则此功能甚至更有用。

constexpr auto maximum_syscall_parameter_count = 7; long execute_system_call_with_arguments(const int value, const int parameters_array[maximum_syscall_parameter_count]) { int return_value_buffer[2]; register int foo0 asm("0") = value; register int foo1 asm("3") = parameters_array[0]; register int foo2 asm("4") = parameters_array[1]; register int foo3 asm("5") = parameters_array[2]; register int foo4 asm("6") = parameters_array[3]; register int foo5 asm("7") = parameters_array[4]; register int foo6 asm("8") = parameters_array[5]; register int foo7 asm("9") = parameters_array[6]; // Execute the syscall asm volatile ("sc" : "+r"(foo3), "+r"(foo4) : "r"(foo0), "r"(foo1), "r"(foo2), "r"(foo5), "r"(foo6), "r"(foo7) ); return_value_buffer[0] = foo3; return_value_buffer[1] = foo4; return *(long *) &return_value_buffer; }
在上面的示例中调用时产生:

.L.main: li 0,9 li 3,1 li 4,2 li 5,3 li 6,4 li 7,5 li 8,6 li 9,7 sc extsw 3,6 blr

保留尽可能多的代码

outside asm模板(约束被视为“外部”)使gcc的优化器可以做各种有用的事情。

其他几点:

    如果parameters_array中的任何项目都是(或可能是)指针,则需要添加memory clobber。这样可以确保在执行asm指令之前,所有可能存储在寄存器中的值都被刷新到内存中。如果不需要(可能)添加内存破坏器,则通过两条指令会减慢执行速度。如果需要,则忽略它可能会导致读取错误的数据。
  1. 如果sc修改了此处未列出的任何寄存器,则必须将它们列为Clobbers。并且,如果此处列出的任何寄存器(foo3和foo4除外)都发生了变化,则还必须使它们成为输入+输出(sc是否将返回码放在foo0中?)。即使您在asm调用之后“不使用它们”,即使它们发生更改,您也必须通知编译器。作为gcc文档explicitly warn
  • 请勿修改仅输入操作数的内容(绑定到输出的输入除外)。编译器假定从asm语句退出时,这些操作数包含的值与执行该语句之前的值相同。

  • 如果不注意此警告,可能会导致代码在一天之内似乎可以正常工作,然后突然在asm块之后(有时很久之后)的某个时刻突然导致奇怪的错误。这种“起作用然后突然不起作用”是我建议您使用don't use inline asm的原因之一,但是如果您

    必须

    (如果需要直接调用sc,则可以这样做) ,请使其尽可能小。
      我通过将maximum_syscall_parameter_count更改为7有点作弊。显然,godbolt的gcc不能通过更多参数来优化此代码。如果有必要,可能有一些解决方法,但是您将需要比我更好的PPC专家来定义它。
    © www.soinside.com 2019 - 2024. All rights reserved.