天行健 发表于 2024-12-22 22:13:14

C++反汇编入门20.在32位环境中的64位值

# 在32位环境中的64位值

在32位环境中的通用寄存器是32位的,所以64位值转化为一对32位值。

## 21.1参数的传递,加法,减法

```
#include <stdint.h>
uint64_t f1 (uint64_t a, uint64_t b)
{
      return a+b;
};
void f1_test ()
{
#ifdef __GNUC__
      printf ("%lld", f1(12345678901234, 23456789012345));
#else
      printf ("%I64d", f1(12345678901234, 23456789012345));
#endif
};
uint64_t f2 (uint64_t a, uint64_t b)
{
      return a-b;
};
```

代码 21.1: MSVC 2012 /Ox /Ob1

```
_a$ = 8                                             ; size = 8
_b$ = 16                                          ; size = 8
_f1   PROC
      mov   eax, DWORD PTR _a$
      add   eax, DWORD PTR _b$
      mov   edx, DWORD PTR _a$
      adc   edx, DWORD PTR _b$
      ret   0
_f1   ENDP

_f1_test    PROC
      push      5461                            ; 00001555H
      push      1972608889                      ; 75939f79H
      push      2874                            ; 00000b3aH
      push      1942892530                      ; 73ce2ff2H
      call      _f1
      push      edx
      push      eax
      push      OFFSET $SG1436 ; ’%I64d’, 0aH, 00H
      call      _printf
      add         esp, 28                         ; 0000001cH
      ret   0
_f1_test    ENDP
_f2   PROC
      mov   eax, DWORD PTR _a$
      sub   eax, DWORD PTR _b$
      mov   edx, DWORD PTR _a$
      sbb   edx, DWORD PTR _b$
      ret   0
_f2   ENDP
```

我们可以看到在函数f1_test()中每个64位值转化为2个32位值,高位先转,然后是低位。加法和减法也是如此。

当进行加法操作时,低32位部分先做加法。如果相加过程中产生进位,则设置CF标志。下一步通过ADC指令加上高位部分,如果CF置1了就增加1。

减法操作也是如此。第一个SUB操作也会导致CF标志的改变,并在随后的SBB操作中检查:如果CF置1了,那么最终结果也会减去1。

在32位环境中,64位的值是从EDX:EAX这一对寄存器的函数中返回的。可以很容易看出f1()函数是如何转化为printf()函数的。

代码 21.2: GCC 4.8.1 -O1 -fno-inline

```
_f1:
      mov   eax, DWORD PTR
      mov   edx, DWORD PTR
      add   eax, DWORD PTR
      adc   edx, DWORD PTR
      ret

_f1_test:
      sub   esp, 28
      mov   DWORD PTR , 1972608889         ; 75939f79H
      mov   DWORD PTR , 5461                ; 00001555H
      mov   DWORD PTR , 1942892530             ; 73ce2ff2H
      mov   DWORD PTR , 2874               ; 00000b3aH
      call    _f1
      mov   DWORD PTR , eax
      mov   DWORD PTR , edx
      mov   DWORD PTR , OFFSET FLAT:LC0      ; "%lld12"
      call    _printf
      add   esp, 28
      ret

_f2:
      mov   eax, DWORD PTR
      mov   edx, DWORD PTR
      sub   eax, DWORD PTR
      sbb   edx, DWORD PTR
      ret
```

GCC代码也是如此。

## 21.2 乘法,除法

```
#include <stdint.h>
uint64_t f3 (uint64_t a, uint64_t b)
{
      return a*b;
};
uint64_t f4 (uint64_t a, uint64_t b)
{
      return a/b;
};
uint64_t f5 (uint64_t a, uint64_t b)
{
      return a % b;
};
```

代码 21.3: MSVC 2012 /Ox /Ob1

```
_a$ = 8                                     ; size = 8
_b$ = 16                                    ; size = 8
_f3   PROC
      push      DWORD PTR _b$
      push      DWORD PTR _b$
      push      DWORD PTR _a$
      push      DWORD PTR _a$
      call      __allmul ; long long multiplication
      ret         0
_f3   ENDP
_a$ = 8                                     ; size = 8
_b$ = 16                                    ; size = 8
_f4   PROC
      push      DWORD PTR _b$
      push      DWORD PTR _b$
      push      DWORD PTR _a$
      push      DWORD PTR _a$
      call      __aulldiv ; unsigned long long division
      ret         0
_f4   ENDP
_a$ = 8                                     ; size = 8
_b$ = 16                                    ; size = 8
_f5   PROC
      push      DWORD PTR _b$
      push      DWORD PTR _b$
      push      DWORD PTR _a$
      push      DWORD PTR _a$
      call      __aullrem ; unsigned long long remainder
      ret         0
_f5   ENDP
```

乘法和除法是更为复杂的操作,一般来说,编译器会嵌入库函数的calls来使用。

部分函数的意义:可参见附录E。

Listing 21.4: GCC 4.8.1 -O3 -fno-inline

```
_f3:
      push      ebx
      mov         edx, DWORD PTR
      mov         eax, DWORD PTR
      mov         ebx, DWORD PTR
      mov         ecx, DWORD PTR
      imul      ebx, eax
      imul      ecx, edx
      mul         edx
      add         ecx, ebx
      add         edx, ecx
      pop         ebx
      ret
_f4:
      sub         esp, 28
      mov         eax, DWORD PTR
      mov         edx, DWORD PTR
      mov         DWORD PTR , eax
      mov         eax, DWORD PTR
      mov         DWORD PTR , edx
      mov         edx, DWORD PTR
      mov         DWORD PTR , eax
      mov         DWORD PTR , edx
      call      ___udivdi3 ; unsigned division
      add         esp, 28
      ret
_f5:
      sub         esp, 28
      mov         eax, DWORD PTR
      mov         edx, DWORD PTR
      mov         DWORD PTR , eax
      mov         eax, DWORD PTR
      mov         DWORD PTR , edx
      mov         edx, DWORD PTR
      mov         DWORD PTR , eax
      mov         DWORD PTR , edx
      call      ___umoddi3 ; unsigned modulo
      add         esp, 28
      ret
```

GCC的做法几乎一样,但是乘法代码内联在函数中,可认为这样更有效。

GCC有一些不同的库函数:参见附录D

## 21.3 右移

```
#include <stdint.h>
uint64_t f6 (uint64_t a)
{
      return a>>7;
};
```

代码 21.5: MSVC 2012 /Ox /Ob1

```
_a$ = 8                                     ; size = 8
_f6   PROC
      mov   eax, DWORD PTR _a$
      mov   edx, DWORD PTR _a$
      shrd    eax, edx, 7
      shr   edx, 7
      ret   0
_f6   ENDP
```

代码 21.6: GCC 4.8.1 -O3 -fno-inline

```
_f6:
      mov   edx, DWORD PTR
      mov   eax, DWORD PTR
      shrd      eax, edx, 7
      shr   edx, 7
      ret
```

右移也是分成两步完成:先移低位,然后移高位。但是低位部分通过指令SHRD移动,它将EDX的值移动7位,并从EAX借来1位,也就是从高位部分。而高位部分通过更受欢迎的指令SHR移动:的确,高位释放出来的位置用0填充。

## 21.4从32位值转化为64位值

```
#include <stdint.h>
int64_t f7 (int64_t a, int64_t b, int32_t c)
{
      return a*b+c;
};

int64_t f7_main ()
{
      return f7(12345678901234, 23456789012345, 12345);
};
```

代码 21.7: MSVC 2012 /Ox /Ob1

```
_a$ = 8                                 ; size = 8
_b$ = 16                              ; size = 8
_c$ = 24                              ; size = 4
_f7   PROC
      push      esi
      push      DWORD PTR _b$
      push      DWORD PTR _b$
      push      DWORD PTR _a$
      push      DWORD PTR _a$
      call      __allmul ; long long multiplication
      mov         ecx, eax
      mov         eax, DWORD PTR _c$
      mov         esi, edx
      cdq               ; input: 32-bit value in EAX; output: 64-bit value in EDX:EAX
      add         eax, ecx
      adc         edx, esi
      pop         esi
      ret         0
_f7   ENDP

_f7_main PROC
      push      12345               ; 00003039H
      push      5461                ; 00001555H
      push      1972608889          ; 75939f79H
      push      2874                ; 00000b3aH
      push      1942892530          ; 73ce2ff2H
      call      _f7
      add   esp, 20               ; 00000014H
      ret   0
_f7_main ENDP
```

这里我们有必要将有符号的32位值从c转化为有符号的64位值。无符号值的转化简单了当:所有的高位部分全部置0。但是这样不适合有符号的数据类型:符号标志应复制到结果中的高位部分。这里用到的指令是CDQ,它从EAX中取出数值,将其变为64位并存放到EDX:EAX这一对寄存器中。换句话说,指令CDQ从EAX中获取符号(通过EAX中最重要的位),并根据它来设置EDX中所有位为0还是为1。它的操作类似于指令MOVSX(13.1.1)。

代码 21.8: GCC 4.8.1 -O3 -fno-inline

```
_f7:
      push      edi
      push      esi
      push      ebx
      mov         esi, DWORD PTR
      mov         edi, DWORD PTR
      mov         ebx, DWORD PTR
      mov         ecx, DWORD PTR
      mov         eax, esi
      mul         edi
      imul      ebx, edi
      imul      ecx, esi
      mov         esi, edx
      add         ecx, ebx
      mov         ebx, eax
      mov         eax, DWORD PTR
      add         esi, ecx
      cdq             ; input: 32-bit value in EAX; output: 64-bit value in EDX:EAX
      add         eax, ebx
      adc         edx, esi
      pop         ebx
      pop         esi
      pop         edi
      ret
_f7_main:
      sub         esp, 28
      mov         DWORD PTR , 12345               ; 00003039H
      mov         DWORD PTR , 1972608889         ; 75939f79H
      mov         DWORD PTR , 5461                ; 00001555H
      mov         DWORD PTR , 1942892530             ; 73ce2ff2H
      mov         DWORD PTR , 2874               ; 00000b3aH
      call
_f7
      add         esp, 28
      ret
```

GCC生成的汇编代码跟MSVC一样,但是在函数中内联乘法代码。 更多:32位值在16位环境中(30.4)
页: [1]
查看完整版本: C++反汇编入门20.在32位环境中的64位值