Skip to content

Latest commit

 

History

History
151 lines (128 loc) · 6.61 KB

8.md

File metadata and controls

151 lines (128 loc) · 6.61 KB

X86_64 Assembly'e merhaba deyin [bölüm 8]

X86_64 Assembly'a merhaba deyin'in sekinci ve son bölümüdür ve burada assembly içinde tam sayı olmayan sayılarla nasıl çalışılacağına bakacağız. Floating point(kayan nokta) verilerle çalışmanın birkaç yolu vardır.

• fpu
• sse

Öncelikle, floating point(kayan nokta) sayıların hafızada nasıl saklandığına bakalım. Üç floating point(kayan nokta) veri tipi vardır:

• single-precision (tek hassasiyet)
• double-precision(çift hassasiyet)
• double-extended precision(çift genişletilmiş hassasiyet)

Intel'in 64-ia-32-architecture-software-developer-vol-1-manual'inde anlatıldığı gibi:

Bu veri türleri için veri biçimleri doğrudan Binary Floating-Point Arithmetic(İkili Kayan Nokta Aritmetiği) için IEEE Standardı 754'te belirtilen biçimlere karşılık gelir.

Bellekte varolan single-precision floating-point(tek hassasiyetli kayan nokta) float point veriler:

• sign(işaret) - 1 bit
• exponent(üs) - 8 bit
• mantissa(ondalık kısım) - 23 bit

Örneğin, eğer aşağıdaki sayıya sahipsek:

| işaret  | üst               | mantissa
|---------|-----------|-----------------------------------
| 0          | 00001111 | 110000000000000000000000

Üs −128 ila 127 arasında 8 bit işaretli bir tam sayı veya 0 ila 255 arasında 8 bit işaretsiz bir tam sayıdır.İşaret biti sıfır bu yüzden pozitif bir sayıya sahibiz.Üs 00001111b veya ondalık olarak 15'tir.Tek hassasiyetli yer değiştirme ( single-precision displacement) 127'dir bu, üs - 127 veya 15 - 127 = -112'yi hesaplamamız gerektiği anlamına gelir.Mantissa'nın normalleştirilmiş ikili tamsayı kısmı her zaman bire eşit olduğundan, o zaman mantissa'ya sadece kesirli kısmı kaydedilir, yani mantissa veya sayımız 1.110000000000000000000000 olur.Sonuç değeri:

değer = mantissa * 2^-112

Çift hassasiyetli sayı 64 bit hafızada:

• işaret - 1 bit
• üs - 11 bit
• mantissa - 52 bit

Alabileceğimiz sonuç sayısı:

değer = (-1)^işaret * (1 + mantissa / 2 ^ 52) * 2 ^ üs - 1023)

Genişletilmiş hassasiyet 80 bit sayılarda:

• işaret - 1 bit
• üs - 15 bit
• mantissa - 112 bit

Bu konuda daha fazla okuyun - burada.Basit örneğe bakalım.

x87 FPU

X87 Floating-Point Unit (Kayan Nokta Ünitesi), yüksek performanslı floating-point işleme sağlar.Floating point, tam sayı ve packed BCD tam sayı veri türlerini ve floating point işleme algoritmalarını destekler.x87 aşağıdaki komut setini sağlar:

Veri transfer komutları
Basit aritmetik komutlar
Kıyaslama komutları
Transandantal komutlar
Load constant instructions 
x87 FPU kontrol komutları

Tabii ki burada x87 tarafından sağlanan tüm komutları görmeyeceğiz, ek bilgi için 64-ia-32-architecture-software-developer-vol-1-manual Bölüm 8'e bakınız. Birkaç veri aktarımı komutu vardır:

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

Aritmetik komutlar

FADD - floating point ekle
FIADD - tam sayıyı floating point'e ekle
FSUB -  floating point çıkar
FISUB -  tam sayıyı floating point'den çıkar
FABS -  mutlak değer al
FIMUL - floating point ve tam sayıyı çarp
FIDIV - floating point ve tam sayıyı böl

ve diğerleri… FPU, bir ring stack'de düzenlenmiş sekiz adet 10 baytlık registera sahiptir. Stack'in tepesi - register ST (0), diğer registerlar ST (1), ST (2)… ST (7). Genellikle floating poin verileriyle çalışırken kullanıyoruz.

Örneğin:

section .data
    x dw 1.0

fld dword [x]

x'in değerini bu stack'e pushlar.Operatör 32bit, 64bit veya 80bit olabilir.Her zamanki gibi stack olarak çalışır, fld ile başka bir değere pushlarsak, x değeri ST (1) 'de ve yeni değer ST (0) olur. FPU komutları bu registerları kullanabilir, örneğin:

; st0 değerini st3 değerine ekler ve st0 içine kaydeder
fadd st0, st3

; x ve y'yi ekler ve st0'a kaydeder
fld dword [x]
fld dword [y]
fadd

Basit bir örneğe bakalım. Daire yarıçapına sahip olacağız ve daire karesini hesaplayacağız ve yazdıracağız:

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

Nasıl çalıştığını anlamaya çalışalım:İlk olarak, önceden tanımlanmış yarıçap verisi ve sonucu depolamak için kullanacağımız sonuç içeren veri bölümü var. Bundan sonra exit system call'u çağırmak için bu 2 sabit.Daha sonra programın giriş noktasını görüyoruz - _start.Burada radius değerini st0 ve st1'de fld komutu ile saklıyoruz ve bu iki değeri fmul komutu ile çarpıyoruz.Bu işlemlerden sonra st0 register'ındaki yarıçap çarpmanın sonucu olacaktır.Ardından fldpi komutuyla π sayısını st0 registerına yüklüyoruz ve sonra yarıçap* yarıçap değeri st1 registerında olacaktır.st0 (pi) ve st1 (yarıçap * yarıçap değeri) üzerinde fmul ile çarpma işlemini gerçekleştirdikten sonra, sonuç st0 registerı olacaktır.Tamam, şimdi st0 register'ında dairenin karesi var ve fstp komutuyla sonucu çıkarabiliriz.Bir sonraki nokta, C fonksiyonuna sonucu aktarmak ve onu çağırmaktır. Hatırlayın, önceki blog yazısında C kodunu assembly kodundan çağırmıştık.X86_64 calling convention bilmemiz gerekiyor.Her zamanki gibi fonksiyon parametrelerini rdi (arg1), rsi (arg2) ve vb. registerları üzerinden geçiririz fakat burada floating point(kayan nokta) verileri var.Burada özel registerlar var: xmm0 - xmm15 sse tarafından sağlanan.Öncelikle, sayıyı xmmN rax registerına koymalı (bizim durumumuz için 0) ve sonucu xmm0 registerına koymalıyız.Şimdi yazdırmak sonucu için C fonksiyonunu çağırabiliriz:

#include <stdio.h>

extern int printResult(double result);

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

Bununla build edebiliriz:

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

Ve çalıştırın:
screenshot2