首页
社区
课程
招聘
[翻译]Say hello to x86_64 Assembly [part 8]
2020-1-14 11:03 6900

[翻译]Say hello to x86_64 Assembly [part 8]

2020-1-14 11:03
6900

Say hello to x86_64 Assembly [part 8]

最近在学习x64汇编,在github上面找到了一点学习资料,入门级别的,因为想细致的学习一下,所以顺便久把作者的内容都翻译了一下,也不知道自己翻译的是否合适,请大家看看有问题的地方请批评指正.第一次做翻译,做的不好请大家原谅,

 

作者原文


 

这是Say hello to x86_64 Assembly的第八部分,也是最后一部分,下面我们将介绍如何在汇编程序中使用非整数。使用浮点数据有两种方法:

  • fpu
  • sse

It is eight and final part of Say hello to x86_64 Assembly and here we will take a look on how to work with non-integer numbers in assembler. There are a couple of ways how to work with floating point data:

  • fpu
  • sse

首先让我们看看浮点数是如何存储在内存中的。有三种浮点数据类型:

  • -单精度
  • -双精度
  • -双扩展精度

First of all let’s look how floating point number stored in memory. There are three floating point data types:

  • single-precision
  • double-precision
  • double-extended precision

如英特尔64-ia-32-architecture-software-developer-vol-1-手册所述:

 

As Intel’s 64-ia-32-architecture-software-developer-vol-1-manual described:

这些数据类型的数据格式直接对应于IEEE标准754中为二进制浮点运算指定的格式。
The data formats for these data types correspond directly to formats specified in the IEEE Standard 754 for Binary Floating-Point Arithmetic.

内存中显示的单精度浮点数据:

  • 符号-1位
  • 指数-8位
  • 尾数-23位

Single-precision floating-point float point data presented in memory:

  • sign - 1 bit
  • exponent - 8 bits
  • mantissa - 23 bits

例如,如果我们有以下数字:

 

So for example if we have following number:

| sign  | exponent | mantissa
|-------|----------|-------------------------
| 0     | 00001111 | 110000000000000000000000

指数可以是-128到127之间的8位有符号整数,也可以是0到255之间的8位无符号整数。符号位是零,所以我们有正数。指数为0000111b或15(十进制)。对于单精度位移是127,这意味着我们需要计算指数-127或15-127=-112。由于尾数的标准化二进制整数部分始终等于1,因此尾数中只记录其小数部分,因此尾数或我们的数字是1110000000000000000000000。结果值为:

 

Exponent is either an 8 bit signed integer from −128 to 127 or an 8 bit unsigned integer from 0 to 255. Sign bit is zero, so we have positive number. Exponent is 00001111b or 15 in decimal. For single-precision displacement is 127, it means that we need to calculate exponent - 127 or 15 - 127 = -112. Since the normalized binary integer part of the mantissa is always equal to one, then in the mantissa is recorded only its fractional part, so mantissa or our number is 1,110000000000000000000000. Result value will be:

value = mantissa * 2^-112

双精度数是64位内存,其中:

  • 符号-1位
  • 指数-11位
  • 尾数-52位

Double precision number is 64 bit of memory where:

  • sign - 1 bit
  • exponent - 11 bit
  • mantissa - 52 bit

结果编号:

 

Result number we can get by:

value = (-1)^sign * (1 + mantissa / 2 ^ 52) * 2 ^ exponent - 1023)

扩展精度是80位数字,其中:

  • 符号-1位
  • 指数-15位
  • 尾数-112位

Extended precision is 80 bit numbers where:

  • sign - 1 bit
  • exponent - 15 bit
  • mantissa - 112 bit

阅读更多信息-这里。让我们看一个简单的例子。

 

Read more about it - here. Let’s look at simple example.

x87 FPU

x87浮点单元(FPU)提供高性能浮点处理。它支持浮点、整数和压缩BCD整数数据类型以及浮点处理算法。x87提供以下指令集:

  • -数据传输指令
  • -基本算术指令
  • -比较说明
  • -超越指令
  • -加载常数指令
  • -x87 FPU控制指令

The x87 Floating-Point Unit (FPU) provides high-performance floating-point processing. It supports the floating-point, integer, and packed BCD integer data types and the floating-point processing algorithms. x87 provides following instructions set:

  • Data transfer instructions
  • Basic arithmetic instructions
  • Comparison instructions
  • Transcendental instructions
  • Load constant instructions
  • x87 FPU control instructions

当然,我们在这里不会看到x87提供的所有说明,有关更多信息,请参阅64-ia-32-architecture-software-developer-vol-1-manual第8章。有一些数据传输指令:

  • FDL - 加载浮点
  • FST - 存储浮点(在ST(0)寄存器中)
  • FSTP - 存储浮点和pop(在ST(0)寄存器中)

Of course we will not see all instructions here provided by x87, for additional information see 64-ia-32-architecture-software-developer-vol-1-manual Chapter 8. There are a couple of data transfer instructions:

  • FDL - load floating point
  • FST - store floating point (in ST(0) register)
  • FSTP - store floating point and pop (in ST(0) register)

算术指令:

  • FADD-添加浮点
  • FIADD-将整数添加到浮点
  • FSUB-减去浮点
  • FISUB-从浮点减去整数
  • FABS-获取绝对值
  • FIMUL-整数与浮点相乘
  • FIDIV-设备整数和浮点

等等…FPU有8个10字节的寄存器组织在一个环形堆栈中。堆栈顶部-寄存器ST(0),其他寄存器是ST(1),ST(2)…ST(7)。我们通常在处理浮点数据时使用它。

 

Arithmetic instructions:

  • FADD - add floating point
  • FIADD - add integer to floating point
  • FSUB - subtract floating point
  • FISUB - subtract integer from floating point
  • FABS - get absolute value
  • FIMUL - multiply integer and floating point
  • FIDIV - device integer and floating point

and etc… FPU has eight 10 byte registers organized in a ring stack. Top of the stack - register ST(0), other registers are ST(1), ST(2) … ST(7). We usually uses it when we are working with floating point data.

 

例子:

 

For example:

section .data
    x dw 1.0

fld dword [x]

将x的值推送到这个堆栈。运算符可以是32位、64位或80位。它和通常的堆栈一样工作,如果我们使用fld推送另一个值,x值将在ST(1)中,新值将在ST(0)中。FPU指令可以使用这些寄存器,例如:

 

pushes value of x to this stack. Operator can be 32bit, 64bit or 80bit. It works as usual stack, if we push another value with fld, x value will be in ST(1) and new value will be in ST(0). FPU instructions can use these registers, for example:

;;
;; adds st0 value to st3 and saves it in st0
;;
fadd st0, st3

;;
;; adds x and y and saves it in st0
;;
fld dword [x]
fld dword [y]
fadd

让我们看一个简单的例子。我们将得到圆半径,计算圆平方并打印出来:

 

Let’s look on simple example. We will have circle radius and calculate circle square and print it:

extern printResult

section .data
        radius    dq  1.7
        result    dq  0

        SYS_EXIT  equ 60
        EXIT_CODE equ 0

global _start
section .text

_start:
        fld qword [radius]
        fld qword [radius]
        fmul

        fldpi
        fmul
        fstp qword [result]

        mov rax, 0
        movq xmm0, [result]
        call printResult

        mov rax, SYS_EXIT
        mov rdi, EXIT_CODE
        syscall

让我们试着理解它是如何工作的:首先,有一个带有预定义的radius数据和结果的数据部分,我们将使用它来存储结果。这2个常量用于调用退出系统调用。接下来我们看到程序的入口点-\u开始。在那里,我们用fld指令将radius值存储在st0和st1中,并用fmul指令将这两个值相乘。在这个操作之后,我们将在st0寄存器中得到半径乘法的结果。接下来,我们用fldpi指令将数字π加载到st0寄存器,然后radiusradius值将在st1寄存器中。在st0(pi)和st1(radiusradius的值)上执行fmul乘法后,结果将在st0寄存器中。好的,现在我们在st0寄存器中有了圆平方,可以用fstp指令将其提取出来。下一点是将结果传递给C函数并调用它。请记住,我们在上一篇博客文章中从程序集代码调用C函数。我们需要知道x86_64调用约定。通常我们通过寄存器rdi(arg1)、rsi(arg2)等传递函数参数,但这里是浮点数据。sse提供特殊寄存器:xmm0-xmm15。首先,我们需要将xmmN寄存器的个数放入rax寄存器(对于我们的情况是0),并将结果放入xmm0寄存器。现在我们可以调用C函数来打印结果:

 

Let’s try to understand how it works: First of all there is data section with predefined radius data and result which we will use for storing result. After this 2 constants for calling exit system call. Next we see entry point of program - _start. There we stores radius value in st0 and st1 with fld instruction and multiply this two values with fmul instruction. After this operations we will have result of radius on radius multiplication in st0 register. Next we load The number π with fldpi instruction to the st0 register, and after it radius radius value will be in st1 register. After this execute multiplication with fmul on st0 (pi) and st1 (value of radius radius), result will be in st0 register. Ok, now we have circle square in st0 register and can extract it with fstp instruction to the result. Next point is to pass result to the C function and call it. Remember we call C function from assembly code in previous blog post. We need to know x86_64 calling convention. In usual way we pass function parameters through registers rdi (arg1), rsi (arg2) and etc…, but here is floating point data. There is special registers: xmm0 - xmm15 provided by sse. First of all we need to put number of xmmN register to rax register (0 for our case), and put result to xmm0 register. Now we can call C function for printing result:

#include <stdio.h>

extern int printResult(double result);

int printResult(double result) {
    printf("Circle radius is - %f\n", result);
    return 0;
}

我们可以用下面命令构建:

 

We can build it with:

build:
    gcc  -g -c circle_fpu_87c.c -o c.o
    nasm -f elf64 circle_fpu_87.asm -o circle_fpu_87.o
    ld   -dynamic-linker /lib64/ld-linux-x86-64.so.2 -lc circle_fpu_87.o  c.o -o testFloat1

clean:
    rm -rf *.o
    rm -rf testFloat1

运行:

 

And run:


[培训]《安卓高级研修班(网课)》月薪三万计划

收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回