qnx下aarch64le和vsx86的无符号的寄存器扩展规则有所不同

文章描述了一段代码在QNXAArch64平台上运行时,由于无符号整数在扩展至64位寄存器时未进行有符号扩展,导致计算结果与预期不符。作者通过分析汇编代码和对比其他平台(如VSx86-64)的行为,发现QNX的编译器在处理无符号类型扩展时可能总是使用无符号扩展,而VS可能使用有符号扩展。通过编写测试用例验证了这一假设,并提出在32位到64位变量转换时可能出现类似问题。
摘要由CSDN通过智能技术生成

0x01起因:

工作时,稳定的老应用代码移植到新平台后,出现的错误,一段复杂类型的计算得到的值和vxworks上、以及windows下用vs x86-64编译调试出的结果不同,再仔细核对代码以及其汇编后发现有该问题,不知是bug还是就是如此设计。

0x02分析:

 上图中的d就是代码出问题的运算的结果,我将这段代码重新命名,方便记录。

前提是:当在其它环境进行运算时,d的结果是‘'000000000000000c’(10进制的12),但是当在qnx用aarch64le编译后运算的结果是'ffffffff0000000c'

(本来想附个图片,但是一直上传失败)

分析:

1、看代码,首先当运算量的类型不同,则先转换成同一类型,然后进行运算。(转换按数据长度增加的方向进行,以保证精度不降低),那最后d运算中的a,b都是扩展到了unsigned long进行的运算。

2、之后去看生成的汇编代码如图所示。(图片上传一直失败,我直接copy过来了)

int main(void) {
   0:    d10083ff     sub    sp, sp, #0x20
    /*- 初始化入口 */
    unsigned char  a = 8;
   4:    52800100     mov    w0, #0x8                       // #8
   8:    39002fe0     strb    w0, [sp,#11]
    unsigned int   b = 5754 - a * 3600;
   c:    39402fe1     ldrb    w1, [sp,#11]
  10:    1281c1e0     mov    w0, #0xfffff1f0                // #-3600
  14:    1b007c20     mul    w0, w1, w0
  18:    11400400     add    w0, w0, #0x1, lsl #12
  1c:    1119e800     add    w0, w0, #0x67a
  20:    b9000fe0     str    w0, [sp,#12]
    unsigned long  c = 5766;
  24:    d282d0c0     mov    x0, #0x1686                    // #5766
  28:    f9000be0     str    x0, [sp,#16]
    unsigned long  d = (c - a * 3600 - b);
  2c:    39402fe1     ldrb    w1, [sp,#11]
  30:    2a0103e0     mov    w0, w1
  34:    531c6c00     lsl    w0, w0, #4
  38:    4b010000     sub    w0, w0, w1
  3c:    531c6c01     lsl    w1, w0, #4
  40:    4b000021     sub    w1, w1, w0
  44:    531c6c20     lsl    w0, w1, #4
  48:    2a0003e1     mov    w1, w0
  4c:    2a0103e0     mov    w0, w1
  50:    93407c00     sxtw    x0, w0
  54:    f9400be1     ldr    x1, [sp,#16]
  58:    cb000021     sub    x1, x1, x0
  5c:    b9400fe0     ldr    w0, [sp,#12]
  60:    cb000020     sub    x0, x1, x0
  64:    f9000fe0     str    x0, [sp,#24]

    return EXIT_SUCCESS;

0x03原因:

那分析完汇编后,其实就找到了问题所在(看了很长时间),看下上述汇编中的5c行和60行,就是原因所在。

首先大概说一下寄存器中的值,和这块的操作。

ldr操作:首先w0获得的就是值b,因为b是int类型的,所以用的是32位寄存器w0来保存b的值。

sub操作:x1是(c-a*3600)的结果,x0是w0寄存器的64位扩展(这块我解释一下,相当于x0是64位的,它的低32位就是w0),所以做的操作就是x0 = x1 - x0.

好的问题终于来了

当w0为正数时,x0与w0相等,当w0为负数时,w0的最高位为1,那么在保证值不变的情况下,x0想与w0的值相同,应该用命令sxtw让w0这个负数做有符号位的扩展,之后扩展后的值给到x0,这样才能保证w0和x0的值相同,但根据汇编代码,发现汇编器并没有做这一步,直接进行的相减。

x1的值为:1111111111111111111111111111111111111111111111111010011000000110

理想中x0的值:1111111111111111111111111111111111111111111111111010010111111010

实际x0的值:0000000000000000000000000000000011111111111111111010010111111010

x1-x0理想中的值 = ‘000000000000000c’

x1-x0在arch64中实际的值为 = ‘ffffffff0000000c’

0x04验证猜想:

但我不觉得这是bug,因为我定义的b是一个unsigned int类型,编译器可能认为是一个正数,所以扩展不扩展是无所谓的,为了验证我这个猜想,我将unsigned int 转为了 int 再一次进行了测试,发现在5c行路由不同,具体如下 

5c:    b9800fe0     ldrsw    x0, [sp,#12]

当我用int定义b时,那么汇编器认为我可能是个有符号的数,所以在从栈帧上那的时候,就给它做了有符号扩展,扩展的数为x0。这么运算就对了。

所以我就在猜想为什么在vs和vxworks上的结果是不同的那,猜想是这样的:

在qnx上arch64拿到一个无符号的值进行寄存器扩展时,那么汇编器对寄存器的扩展恒为无符号扩展

在vsx86上拿到一个无符号的值进行寄存器扩展时,那么汇编器对寄存器的扩展是恒为有符号扩展

之后我那vs和qnx下分别写了一个测试用例证明我的猜想,代码如下 :

   unsigned long a = 500;
   unsigned int b = 0xffffffff;
   unsigned long c = a + b;
   printf("%p", c);

在vs x86-64的环境下给出的结果是:00000000000001F3 

解释:因为vs进行的是有符号扩展,所以0xffffffff->0xffffffffffffffff等于-1,500+-1=499,最后的结果就是1F3。

在qnx aarch64le的环境下给出的结果是:1000001f3

解释:因为qnx进行的是无符号扩展,所以0xffffffff->0x00000000ffffffff等于4294967295,500+4,294,967,295=4,294,967,795最后的结果就是1000001f3

和我猜想的一样。

我又将long int 变为 int short做了一次测试,但是结果和我上面的结论有些不同,根据产生的汇编文件研究发现,因为short类型的变量,汇编还是拿的32位寄存器存的,所以不存在对寄存器的扩展,避免了上述的情况。

0x05总结:

在qnx上arch64拿到一个无符号的值进行寄存器扩展时,那么汇编器对寄存器的扩展恒为无符号扩展

在vsx86上拿到一个无符号的值进行寄存器扩展时,那么汇编器对寄存器的扩展是恒为有符号扩展

在现实应用中,这种情况大多出现在32位变量到64位变量的转化的场景,因为根据测试发现(64位的环境)小于等于32位的变量汇编语言大多都用的是32位寄存器,就不存在上述的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值