为什么va_arg()在x86_64和arm上产生不同的效果? [英] Why va_arg() produce different effects on x86_64 and arm?

查看:473
本文介绍了为什么va_arg()在x86_64和arm上产生不同的效果?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

代码:

  #include  
#include< stdarg.h>
#include< stdlib.h>

typedef unsigned int uint32_t;

平均值(int n_values,...)
{
va_list var_arg;
int count;
float sum = 0;

va_start(var_arg,n_values); (count = 0; count< n_values; count + = 1){
sum + = va_arg(var_arg,signed long long int);


}

va_end(var_arg);

返回sum / n_values;

$ b $ int main(int argc,char * argv [])
{
(void)argc;
(void)argv;

printf(hello world!\\\
);

uint32_t t1 = 1;
uint32_t t2 = 4;
uint32_t t3 = 4;
printf(result:%f\\\
,average(3,t1,t2,t3));

返回0;
}

当我在ubuntu(x86_64)中运行时,没关系。

  lix @ lix-VirtualBox:〜/ test / c $ ./a.out 
hello world!
结果:3.000000
lix @ lix-VirtualBox:〜/ test / c $ uname -a
Linux lix-VirtualBox 4.4.0-116-generic#140-Ubuntu SMP Mon Feb 12 21 :23:04 UTC 2018 x86_64 x86_64 x86_64 GNU / Linux
lix @ lix-VirtualBox:〜/ test / c $

但是当我交叉编译并在openwrt(ARM 32bit)中运行它时,它是错误的。

  [root @ OneCloud_0723:/ root / lx]#。/ helloworld 
hello world!
结果:13952062464.000000
[root @ OneCloud_0723:/ root / lx] #uname -a
Linux OneCloud_0723 3.10.33#1 SMP PREEMPT Thu Nov 2 19:55:17 CST 2017 armv7l GNU / Linux

我知道不要使用不正确类型的参数调用va_arg。但是,为什么我们可以在x86_64中获得正确的结果呢?



谢谢。

解决方案<在x86-64 Linux上,每个32位arg都在一个单独的64位寄存器中传递(因为这是x86-64 System V调用约定所要求的)。 p>

调用者恰好将32位参数零扩展到64位寄存器中。 (这不是必需的;程序中的未定义行为可能会使用不同的调用方在arg-passing寄存器中留下高垃圾。)



被调用者 average())正在寻找三个64位参数,并且在调用者放置它们的相同寄存器中查找,所以它恰好工作。






在32位ARM上, long long 不适合一个注册表,所以寻找 long long args的被调用者肯定看到的地方不同于调用者放置 uint32_t

被调用者看到的第一个64位arg可能是((long long)t1 <<32) t2 ,或者相反。但是由于被调用者正在寻找6×32位的参数,它会查看调用者根本不打算作为参数的寄存器/存储器。

(注意这可能会导致调用者在堆栈上的本地人腐败,因为被调用者被允许打开堆栈参数。)






有关完整的详细信息,请使用编译器+编译选项查看代码的asm输出,以查看源代码中C未定义行为的具体行为。 objdump -d ./helloworld 应该这样做,或直接查看编译器输出:如何去除噪声从GCC / clang程序集输出?


The codes:

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>

typedef unsigned int uint32_t;

float average(int n_values, ... )
{
    va_list var_arg; 
    int count;
    float sum = 0;

    va_start(var_arg, n_values);

    for (count = 0; count < n_values; count += 1) {
        sum += va_arg(var_arg, signed long long int);
    }   

    va_end(var_arg);

    return sum / n_values;
}

int main(int argc, char *argv[])
{
    (void)argc;
    (void)argv;

    printf("hello world!\n");

    uint32_t t1 = 1;  
    uint32_t t2 = 4;  
    uint32_t t3 = 4;  
    printf("result:%f\n", average(3, t1, t2, t3));

    return 0;
}

When I run in ubuntu (x86_64), It's Ok.

lix@lix-VirtualBox:~/test/c$ ./a.out 
hello world!
result:3.000000
lix@lix-VirtualBox:~/test/c$ uname -a
Linux lix-VirtualBox 4.4.0-116-generic #140-Ubuntu SMP Mon Feb 12 21:23:04 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
lix@lix-VirtualBox:~/test/c$ 

But when I cross-compiler and run it in openwrt(ARM 32bit), It's wrong.

[root@OneCloud_0723:/root/lx]#./helloworld 
hello world!
result:13952062464.000000
[root@OneCloud_0723:/root/lx]#uname -a
Linux OneCloud_0723 3.10.33 #1 SMP PREEMPT Thu Nov 2 19:55:17 CST 2017 armv7l GNU/Linux

I know do not call va_arg with an argument of the incorrect type. But Why we can get right result in x86_64 not in arm?

Thank you.

解决方案

On x86-64 Linux, each 32-bit arg is passed in a separate 64-bit register (because that's what the x86-64 System V calling convention requires).

The caller happens to have zero-extended the 32-bit arg into the 64-bit register. (This isn't required; the undefined behaviour in your program could bite you with a different caller that left high garbage in the arg-passing registers.)

The callee (average()) is looking for three 64-bit args, and looks in the same registers where the caller put them, so it happens to work.


On 32-bit ARM, long long is doesn't fit in a single register, so the callee looking for long long args is definitely looking in different places than where the caller placed uint32_t args.

The first 64-bit arg the callee sees is probably ((long long)t1<<32) | t2, or the other way around. But since the callee is looking for 6x 32 bits of args, it will be looking at registers / memory that the caller didn't intend as args at all.

(Note that this could cause corruption of the caller's locals on the stack, because the callee is allowed to clobber stack args.)


For the full details, look at the asm output of your code with your compiler + compile options to see what exactly what behaviour resulted from the C Undefined Behaviour in your source. objdump -d ./helloworld should do the trick, or look at compiler output directly: How to remove "noise" from GCC/clang assembly output?.

这篇关于为什么va_arg()在x86_64和arm上产生不同的效果?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆