# ASK 2020 GR. Kraski cz I
## Lista 3 (26.03.2020)
### Zadanie 1
:::info
Zastąp instrukcję dzielenia całkowitoliczbowego zmiennej $n$ typu ```int32_t``` przez stałą 3 przy pomocy operacji mnożenia liczb typu ```int64_t```. Skorzystaj z faktu, że $\frac{x}{k} \equiv x \cdot \frac{1}{k}$. Przedstaw dowód poprawności swojego rozwiązania. Instrukcja dzielenia działa zgodnie z wzorem podanym na wykładzie, tj.:
$$
\texttt{div3}(n) =
\begin{cases}
\lfloor \frac{n}{3} \rfloor & \text{dla } n \geq 0 \\
\lceil \frac{n}{3} \rceil & \text{dla } n < 0
\end{cases}
$$
:::
```c=
x = 0x55555556
y = ((x * n) >> 32)
y = y + ((n>>31) & 1);
```
Dowód:
Zauważmy, że $x = \frac{2^{32}+2}{3}$
1. Dla $n \geq 0$
y = x * n >> 32 czyli $\frac{x \cdot n}{2^{32}}$
Rozipisując,
$$
y = \Big\lfloor \frac{2^{32} \cdot n + 2 \cdot n}{3 \cdot 2^{32}} \Big\rfloor =
\Big\lfloor \frac{n}{3} + \frac{2 \cdot n}{3 \cdot 2^{32}} \Big\rfloor
$$
2. dla $n < 0$
y = x * n >> 32 + 1, czyli $\frac{x\cdot n}{2^{32}} + 1$
Rozipisując
$$
y = \Big\lfloor \frac{2^{32} \cdot n + 2 \cdot n + 3 \cdot 2^{32}}{3 \cdot 2^{32}} \Big\rfloor =
\Big\lceil \frac{2^{32} \cdot n + 2 \cdot n + 1}{3 \cdot 2^{32}} \Big\rceil =
\Big\lceil \frac{n}{3} + \frac{2 \cdot n + 1}{3 \cdot 2^{32}} \Big\rceil
$$
Przejście z podłogi do sufitu:
$$
\Big\lfloor \frac{n + d - 1}{d} \Big\rfloor = \Big\lceil \frac{n}{d} \Big\rceil
$$
W obu przypadkach "reszta", która nam została jest bardzo mała i nie zmienia wyniku w przypadku, gdyby jej nie było.
### Zadanie 2
:::info
Standard IEEE 754-2008 definiuje liczby zmiennopozycyjne o szerokości 16-bitów. Zapisz ciąg bitów reprezentujący liczbę $1.5625 \cdot 10^{-1}$. Porównaj zakres liczbowy i dokładność w stosunku do liczb zmiennopozycyjnych pojedynczej precyzji (```float```).
:::
Odpowiedz:
0.15625 = 0b0 01100 0100000000
halfy sa postaci: 1 bit znaku, 5 bitow cechy, 10 bitow mantysy
najpierw zapisujemy 0.15625 jako fixed precision:
0.00101 przesuwamy . o 3 miejsca w prawo (mnozymy przez 2^-3)
otrzymujemy 1.01
bias dla half'ów to 15 wiec chcemy zakodowac 15 - 3 = 12
s = 0
exp = 01100 = 12
frac = 0100000000
czyli 0.15625 = 0b0 01100 0100000000
zakres liczbowy single precision (float):
[-3.4 * 10^38, 3.4 * 10^38]
dokladnosc single precision (float):
24/log2(10) co daje 7 znaczacych cyfr po przecinku
zakres liczbowy half precision:
[-65504,65504]
* największa liczba - 2^15 * (2-1/2^10^)
* najmniejsza dodatnia wartość znormalizowana - 2^-14^ * 1
* najmniejsza wartość dodatnia - 2^-14^ *1/2^10^ = 2^-24^
dokladnosc half precision:
11/log2(10) co daje 3 znaczace cyfry po przecinku
### Zadanie 3
:::info
Oblicz ręcznie $3.984375 \cdot 10^{-1} + 3.4375 \cdot 10^{-1} + 1.771 \cdot 10^{3}$ używając liczb w formacie z poprzedniego zadania. Zapisz wynik binarnie i dziesiętnie. Czy wynik się zmieni jeśli najpierw wykonamy drugie dodawanie?
:::
Najpierw dostosujmy liczby do naszej reprezentacji:
$3.984375 \cdot 10^{-1} = 0.0110011_{2} = 1.10011_{2} \cdot 2^{-2}$
$3.4375 \cdot 10^{-1} = 0.01011_{2} = 1.011_{2} \cdot 2^{-2}$
$1.771 \cdot 10^{3} = 11011101011 = 1.1011101011 \cdot 2^{10}$
Przystępujemy do obliczania pamiętając o wyrównaniu, by uzyskać takie same wykładniki(dodając) oraz o zasadach zaokrąglania (round to even):
$1.10011_{2} \cdot 2^{-2} + 1.011 \cdot 2^{-2} + 1.1011101011_{2} \cdot 2^{10}=$
$10.0011_{2} \cdot 2^{-2} + 1.1011101011_{2} \cdot 2^{10}=$
$1.011111_{2} \cdot 2^{-1} + 1.1011101011_{2} \cdot 2^{10}=$
$(0.00000000001011111_{2} + 1.1011101011_{2}) \cdot 2^{10}=$
$1.101110101110_{2} \cdot 2^{10} = 1.10111011_{2} \cdot 2^{10} = 1772_{10}$
Zamieniając kolejność:
$1.011 \cdot 2^{-2} + 1.1011101011_{2} \cdot 2^{10} + 1.10011_{2} \cdot 2^{-2}=$
$(0.000000000001011_{2} + 1.1011101011_{2}) \cdot 2^{10} + 1.10011_{2} \cdot 2^{-2}=$
$1.1011101011_{2} \cdot 2^{10} + 1.10011_{2} \cdot 2^{-2} =$
$1.1011101011_{2} \cdot 2^{10} = 1771_{10}$
Wyniki różnią się w zależności od tego, w jakiej kolejności wykonywaliśmy dodawanie. Powodem jest to, że po każdym dodawaniu musimy zaokrąglić liczbę tak, aby zmieściła się w naszym typie, stąd dodając bardzo małą liczbę do dużej możemy ją 'zgubić', jeśli nie jest wystarczająco znacząca (tak stało się przy zamienieniu kolejności działania).
### Zadanie 4
:::info
Mamy zmienne $x$, $y$ i $z$ typu ```int32_t``` ustawione na wybrane przez nas wartości. Konwertujemy je do liczb typu ```double``` zapisanych w zmiennych $dx$, $dy$ i $dz$. Rozważmy poniższe wyrażenia z języka ```C```. Wskaż, które z nich mogą obliczyć się do fałszu. Podaj kontrprzykład – konkretne wartości naszych zmiennych całkowitoliczbowych.
* ```(float)x == (float)dx```
* ```dx - dy == (double)(x - y)```
* ```(dx + dy) + dz == dx + (dy + dz)```
* ```(dx * dy) * dz == dx * (dy * dz)```
* ```dx / dx == dz / dz```
:::
* ```(float)x == (float)dx``` - Zawsze true
Zmiana z int na double jest bezstratna i po zmianie na float w równianiu
otrzymujemy to samo.
* ```dx - dy == (double)(x - y)```
Kontrprzykład: x = INT_MIN i y = 1
Po lewej stronie wykona się normalnie odejmowanie,
a natomiast po prawej będziemy mieli underflow i dopiero
po tym zmiane na double.
* ```(dx + dy) + dz == dx + (dy + dz)``` - Zawsze true
Dodawanie nigdy nie wyjdzie poza zakres nawet jak każda
z liczb będzie równa INT_MAX, bo 3*INT_MAX < DBL_MAX
Tak samo jeśli chodzi o underflow.
Z racji, że operujemy na liczbach rzędu 2^33^, spokojnie mieścimy się w zakresie double.
* ```(dx * dy) * dz == dx * (dy * dz)```
Wybierając duże, przypadkowe x,y,z może zdarzyć sie, że
nastąpi zaokrąglenie które sprawi, że otrzymamy fałsz.
Z racji, że operujemy na liczbach rzędu 2^96^, czasami potrzebne jest zaokrąglanie, które może sprawić, że dostaniemy różne wyniki.
* ```dx / dx == dz / dz```
Kontrprzykład: x = 1 y = 0
Po lewej stronie otrzymamy 1 natomiast po prawej będziemy
mieli dzielenie 0/0 i dostaniemy jakąś przpadkową liczbę.
### Zadanie 5
:::info
Reprezentacje binarne liczb zmiennoprzecinkowych $f$ i $g$ typu ```float``` zostały załadowane odpowiednio do zmiennych ```x``` i ```y``` typu ```uint32_t```. Podaj wyrażenie, które:
* zmieni znak liczby ```x```,
* obliczy wartość $\lfloor log_2 |\texttt{f}| \rfloor$ typu ```int``` dla $f$ w postaci znormalizowanej,
* zwróci wartość logiczną operacji ```x == y```,
* zwróci wartość logiczną operacji ```x < y```.
Pamiętaj, że dla liczb zmiennopozycyjnych w standardzie IEEE 754 zachodzi $-0 \equiv +0$. Można pominąć rozważanie wartości ```NaN```.
:::
1. Należy zmienić bit znaku na przeciwny: ```x ^= (1 << 31);```
2. Korzystamy z informacji zapisanych w EXP: ```((x >> 23) & 255) - 127;```
3. Specjalny przypadek $0 = -0$: ```(x == y) | (x | y == (1 << 31))```
4. Porównujemy, znowu $0 = -0$ jest problemem:
```(x>=0 && x<y) || (x<0 && x>y)```
```(((~(x | y) & (x - y)) | (x & y & (y - x)) | (x & ~y)) & (1ll << 31)) != 0 & ((x | y) != (1ll << 31))```
> Zauważmy, że jeśli liczby są tego samego znaku, to najpierw powinniśmy porównać ich wykładniki, a jeśli są równe, to porównać mantysy. Bity liczby są akurat tak ustawione, że tak naprawdę wystarczy porównać ich wartości bitowe. Do tego wystarczy orównać znak ich różnicy. Zatem aby wykonać zadanie musimy:
> * jeśli obie są dodatnie, to sprawdzić znak x-y,
> * jeśli obie są ujemne, to sprawdzić znak y-x,
> * jeśli x jest ujemne a y dodatnie to zwracamy prawdę,
> * na końcu sprawdzić czy nie są zerami.
>
$$
-Nan < -\infty < -Norm < -Denorm \leq +Denorm < Norm < +\infty < +Nan
$$
### Zadanie 6
:::info
Reprezentacja binarna liczby zmiennoprzecinkowej $f$ typu ```float``` została załadowana do zmiennej ```x``` typu ```uint32_t```. Podaj algorytm obliczający $f \cdot 2^i$ wykonujący obliczenia na zmiennej ```x``` używając wyłącznie operacji na liczbach całkowitych. Osobno rozważ $i \ge 0$ i $i < 0$. Zakładamy, że liczba $f$~jest znormalizowana, ale wynik operacji może dać wartość $\pm \infty$, $\pm 0$ lub liczbę zdenormalizowaną. Możesz założyć, że wynik zaokrąglamy w kierunku zera.
:::
#### Rozwiązanie:
```c=
int e = ((x & 0x7f800000) >> 23) - 127;
e = e + i;
if (e>127)
x=0x7FF0000000000000 + x^(1<<31);
else if (-150 <= e && e <= -127)
x = (x & 0x80000000) + ((x&0x007fffff) >> -(e + 127));
else if (e < -150)
x = 0;
else
x = (x&807fffff) + (127 + e)<<23;
```
do wyliczenia korzystamy ze wzoru:
$(-1)^S \cdot M \cdot 2^{i + E}$
gdzie M to Mantysa, E to cecha, S to bit znaku
###### obliczamy i + exponent
```c=
int e = ((x & 0x7f800000) >> 23) - 127;
e = e + i;
```
###### Jeżeli jest za duże to ustawiamy x = INF
```c=
if (e>127)
x=0x7FF0000000000000 + x^(1<<31);
```
###### na tym przedziale wynik jest zdenormalizowany
```c=
else if (-150 <= e && e <= -127)
x = (x & 0x80000000) + ((x&0x007fffff) >> -(e + 127));
```
###### Jeżeli za mała liczba - ustwiamy x = 0 (pomijamy znak 0)
```c=
else if (e < -150)
x = 0;
```
###### w każdym innym wypadku wynik jest znormalizowany
```c=
else
x = (x&807fffff) + (127 + e)<<23;
```
### Zadanie 7
:::info
Masz liczbę ```double``` jako ```uint64_t```. Wyprodukuj ```float``` z zaokręgleniem do najbliższej parzystej. Podaj definicje bitów guard, round, sticky.
:::
>Zanim przystąpimy do rozwiązania należy zwrócić uwagę na format double i float:
>
>$double$
>|sign |exponent| fraction|
>|------|--------|---------|
>|1 bit |11 bits | 52 bits |
>$float$
>|sign |exponent| fraction|
>|------|--------|---------|
>|1 bit | 8 bits | 23 bits |
>
> Double zapewnia nam większą dokładność, dlatego musimy zaokrąglić mantysę zgodnie z założeniami round to even.
>
>
> Ponieważ na zakodowanie wykładnika float mamy 8 bitów to:
> $E_{minn} = -126$ - dla liczb w postaci znormalizowanej
> $E_{minz} = -149$ - dla liczb w postaci zdenormalizowanej
> $E_{max} = 127$
>
> Konwertując musimy zatem rozważyć następujące przypadki brzegowe:
> * Jeśli wyliczone $E_{double} > 127$ zwracamy +∞,
> * Gdy $E_{double} < -149$ zwracamy 0,
> * W przypadku $-149 ≤ E_{double} ≤ -126$ zwracamy liczbę zdenormalizowaną.
```cpp
uint32_t double_to_float(uint64_t x)
{
// obliczamy każdą część naszej liczby double
int32_t sign = (x >> 63) & 1;
/*dekodujemy wykładnik i kodujemy go na wykładnik float:
(x >> 52) & ((1 << 11) - 1) <- wyodrębnienie EXPd
1023 <- Bias double
127 <- Bias float */
int32_t e = (x >> 52) & ((1 << 11) - 1) - 1023 + 127;
int64_t frac = x & ((1 << 52) - 1);
//zaczynamy obliczać części do floata
/* wyodrębniamy pierwsze 23 bity pierwotnej mantysy*/
int32_t new_frac = (frac >> 29);
//wartości do zaokrąglania
/*
guard - najmniej znaczący bit wyniku
round - najbardziej znaczący usuwany bit
sticky - OR pozostałych usuwanych bitów
*/
int32_t guard = (frac >> 29) & 1;
int32_t round = (frac >> 28) & 1;
int32_t sticky = (frac & 0xFFFFFFF) != 0;
//0xFFFFFFF wyodrębnia 28 dolnych bitów
// zaokrąglenie
/*
1.gdy round = sticky = 1 mamy przypadek, w którym jesteśmy w
'ponad połowie'
do następnej liczby i zaokrąglamy
2.gdy round = guard = 1 mamy przypadki
* jeśli sticky = 0 to jesteśmy w połowie i zaokrąglamy
do najbliższej parzystej (w górę)
* jeśli sticky = 1 to przypadek 1.
*/
if (round == 1 && (sticky == 1 || guard == 1))
new_frac++;
//sprawdzamy czy zaokrąglając mantysę nie przekroczymy 2
if (new_frac >= (1 << 23))
{
e++;
new_frac ^= (1 << 23);
}
// jeśli mamy zbyt duży wykładnik, zwracamy inf
if (e > 253)
return (sign << 31) | 0x7F800000;
// jeśli mamy zbyt mały wykładnik, zwracamy 0
if (e < -23)
return 0;
// jeśli wykładnik wpada w przedział zdenormalizowanych
if (e >= -23 && e <= 0)
{
//przesuwamy mantysę
new_frac = new_frac >> -e;
e = 0;
}
return (sign << 31) | (e << 23) | new_frac;
}
```
### Zadanie 8
:::info
Uzupełnij ciało funkcji zadeklarowanej następująco:
```cpp
/* Skonwertuj reprezentację liczby float do wartości int32_t. */
int32_t float2int(int32_t f);
```
Zaokrąglij liczbę w kierunku zera. Jeśli konwersja spowoduje nadmiar lub $f$ ma wartość ```NaN```, zwróć ```0x80000000```. Dla czytelności napisz najpierw rozwiązanie z instrukcjami warunkowymi. Potem przepisz je, by zachować zgodność z wytycznymi z nagłówka listy.
Uwaga: prowadzący zezwala na użycie instrukcji ```if``` i porównań.
:::
```c=
int float2int(int f){
//0x7FFFFF = 0b00000000011111111111111111111111
//0x7F800000 = 0b01111111100000000000000000000000
//1.
/*
Wyciągamy wszystkie części float'a
i dosuwamy je do prawej strony.
Każdy bit sign jest równy bitowi znaku.
*/
int sign = f>>31;//znak
int mant = 0x7FFFFF&f;//mantysa
int exp = (0x7F800000&f)>>23;//cecha
//2.
/*
(mant | 1<<23) - dopisujemy do mantysy
ukrytą jedynkę sprzed przecinka.
(exp-127) - pozbywamy się uprzedzenia cechy
Przemnażamy mantysę przez 2^cecha - uzyskujemy
wartość bezwzględną wyniku.
+-23 - bierzemy pod uwagę, że mantysa, to tak naprawdę liczby po przecinku
*/
/*
sign jest całe wypełnione tym samym bitem,
dlatego and'owanie z sign działa.
Jeśli f jest ujemna, to odwracamy res,
inaczej zostawiamy bez zmian.
*/
int res;
if(sign){
res = (mant | 1<<23)>>(exp-127+23);
res = -res;
}
else{
(mant | 1<<23)<<(exp-127-23);
}
//bitowo:
/*
int res = sign & (mant | 1<<23)>>(exp-127+23) + ~sign & (mant | 1<<23)<<(exp-127-23);
res = sign & -res + ~sign & res;
*/
//3.
/*
Sprawdzamy NaN i infinity(oba mają mantysę równą 0b11111111).
(b<<31)>>31 propaguje nam wartość b[0] na każdy
bit (czego potrzebujemy, by nasz bitowy "if" działał).
*/
if(exp==0b11111111){
res = 0x80000000;
}
//bitowo:
int mask = ((exp==0b11111111)<<31)>>31
res = mask & 0x80000000 + ~mask & res;
//4.
/*
Sprawdzamy nadmiar.
Nasz wynik zajmuje 24 najmłodsze bity res.
Zatem największe exp nie powodujące nadmiaru to 7
(bo potem wejdziemy z ukrytym bitem sprzed przecinka
(dopisanym w 2.) na bit znaku).
*/
//można to zapisać bitowo, ale dla czytelności zostawmy tak
if(exp>7){
res = 0x80000000;
}
return res;
}
```
# Lista 4 (02.04.2020)
### Zadanie 1
:::info
Poniżej podano wartości typu ```long``` leżące pod wskazanymi adresami i w rejestrach:
|Adres | Wartość| | Rejestr| Wartość|
|-------|--------|-|--------|--------|
| 0x100 | 0xFF | | %rax | 0x100
| 0x108 | 0xAB | | %rcx | 1
| 0x110 | 0x13 | | %rdx | 3
| 0x118 | 0x11 | |
Oblicz wartość poniższych operandów:
1. %rax
2. 0x110
3. $0x108
4. (%rax)
5. 8(%rax)
6. 21(%rax,%rdx)
7. 0xFC(,%rcx,4)
8. (%rax,%rdx,8)
9. 265(%rcx,%rdx,2)
:::
1) `%rax` zwróci nam $0\text{x}100$
2) `0x110` zwróci nam to, co przechowuje, mianowicie $0\text{x}13$
3) `$0x108` to stała, więc wynik wynosi $0\text{x}108$
4) `(%rax)` zwraca to, co jest pod adresem równym wartości `%rax`, czyli $0\text{x}FF$
5) `8(%rax)` obliczy $0\text{x}100+8=0\text{x}108$, pod której adresem znajduje się $0$x$AB$
6) `21(%rax, %rdx)` oblicza się do $(21+0\text{x}100+0\text{x}3)=(0\text{x}15+0\text{x}100+0\text{x}3)=0\text{x}118$ pod którym kryje się $0\text{x}11$
7) `0xFC(,%rcx,4)` obliczy się do $4*0\text{x}1+0\text{xFC} = 0\text{x}100$ pod którym kryje się $0\text{xFF}$
8) `(%rax, %rdx,8)` obliczy się do $0\text{x}100 + 8*0\text{x}3 = 0\text{x}100 + 0\text{x}18 = 0\text{x}118$ pod którym kryje się $0\text{x}11$
9) `265(%rcx, %rdx,2)` obliczy się do $0\text{x}1 + 2*0\text{x}3 + 0\text{x}109 = 0\text{x}7 + 0\text{x}109 = 0\text{x}110$ pod którym kryje się $0\text{x}13$
### Zadanie 2
:::info
Każdą z poniższych instrukcji wykonujemy w stanie maszyny opisanym tabelką z poprzedniego zadania. Wskaż miejsce, w którym zostanie umieszczony wynik działania instrukcji, oraz obliczoną wartość.
```assembly=
addq %rcx,(%rax)
subq 16(%rax),%rdx
shrq $4,%rax
incq 16(%rax)
decq %rcx
imulq 8(%rax)
leaq 7(%rcx,%rcx,8),%rdx
leaq 0xA(,%rdx,4),%rdx
```
:::
1.addq %rcx, (%rax)
(%rax) = (%rax) + %rcx
0x100 = 0xFF + 0x1
0x100 = 0x100
2.subq 16(%rax), %rdx
%rdx = %rdx - 0x10(%rax)
%rdx = 0x3 - (0x100 + 0x10)
%rdx = 0x3 - (0x110)
%rdx = 0x3 - 0x13
%rdx = 0xFFFFFFF0
3.shrq $4, %rax // shrq src,dest -> dest = dest >> src
%rax = %rax >> 0x4
%rax = 0x100 >> 0x4
%rax = 0x10
4.incq 16(%rax)
0x10(%rax) = 0x10(%rax) + 0x1
0x110 = (0x110) + 0x1
0x110 = 0x13 + 0x1
0x110 = 0x14
5.decq %rcx
%rcx = 0x1 - 0x1
%rcx = 0x0
6.imulq 8(%rax)
%rdx: %rax = 8(%rax) * %rax
%rdx: %rax = (0x100 + 0x8) * 0x100
%rdx: %rax = (0x108) * 0x100
%rdx: %rax = 0xAB * 0x100
%rdx: %rax = 0xAB00
7.leaq 7(%rcx, %rcx, 8), %rdx
%rdx = %rcx + 8*%rcx + 0x7
%rdx = 0x1 + 0x8 + 0x7
%rdx = 0x10
8.leaq 0xA(,%rdx,4),%rdx
%rdx = 0x4 * 0x3 + 0xA
%rdx = 0xC + 0xA
%rdx = 0x16
### Zadanie 3
:::info
W rejestrach %rdi i %rsi przechowujemy wartość zmiennych ```x``` i ```y```. Porównujemy je instrukcją ```cmp %rsi,%rdi```. Jakiej instrukcji należy użyć, jeśli chcemy skoczyć do etykiety ```label``` gdy:
1. ```x``` był wyższy lub równy ```y```,
2. ```y``` nie był mniejszy lub równy ```x```,
3. ```x``` nie był niższy lub równy ```y```,
:::
```
cmp %rsi, %rdi # (%rsi trzyma y, %rdi trzyma x)
```
cmp ustawia flagi dla operacji x-y ( można na to patrzeć jak na x + (-y) )
Flagi modyfikowane przez cmp:
| flaga | | |
| ----- | --------------- |:--------------------------------------------------------------------:|
| CF | "Carry Flag" | Wystąpił Overflow dla liczb Unsigned (dodawanie z "pożyczką") |
| OF | "Overflow Flag" | Wystąpił Overflow dla liczb Signed |
| SF | "Sign Flag" | Mówi czy w wyniku operacji zapalił się bit znaku |
| ZF | "Zero Flag" | Mówi czy operacja dała wynik 0 |
| PF | "Parity Flag" | Mówi czy w wyniku operacji dostaliśmy liczbę z parzystą liczbą bitów |
#### 1. x wyższy lub równy y
(wyższy - większy w znaczeniu Unsigned)
jae - Jump if above or equal (też jnb)
wykonuje skok jeżeli CF = 0
```
jae <<label>> #~CF
```
#### 2. y nie był mniejszy lub równy x
!(y<=x) czyli x<y
a więc: x-y < 0
jl - jump if less (signed) (też jnge)
wykonuje skok gdy tylko i wyłącznie jedna z flag SF lub OF = 1
```
jl <<label>> #SF^0F
```
cmp zapali flagę SF bo liczba x-y < 0
#### 3. x nie był niższy lub równy y
(niższy - mniejszy w znaczeniu unsigned)
!(x <= y) <=> (x > y)
ja - Jump if above (unsigned) (też jnbe)
wykonuje skok gdy CF = 0 i SF = 0
```
ja <<label>> #~CF&~SF
```
### Zadanie 4
:::info
Zaimplementuj w asemblerze x86-64 procedurę konwertującą liczbę typu ```uint32_t``` między formatem little-endian i big-endian. Argument funkcji jest przekazany w rejestrze %edi, a wynik zwracany w rejestrze %eax. Należy użyć instrukcji cyklicznego przesunięcia bitowego ```ror``` lub ```rol```.
Podaj wyrażenie w języku C, które kompilator optymalizujący przetłumaczy do instrukcji ```ror``` lub ```rol```.
:::
```=
%edi = 0x4A 3B 2C 1D
ROLW %di, 8 -> %edi = 0x4A 3B 1D 2C
ROLL %edi, 16 -> %edi = 0x1D 2C 4A 3B
ROLW %di, 8 -> %edi = 0x1D 2C 3B 4A
MOVL %edi,%eax -> %eax = 0x1D 2C 3D 4A
```
Wyrażenie w C:
```c=
x=(x<<count)|(x>>(32-count))
```
### Zadanie 5
:::info
Zaimplementuj w asemblerze x86-64 funkcję liczącą wyrażenie ```x + y```. Argumenty i wynik funkcji są 128-bitowymi liczbami całkowitymi ze znakiem i nie mieszczą się w rejestrach maszynowych. Zatem ```x``` jest przekazywany przez rejestry %rdi (starsze 64 bity) i %rsi (młodsze 64 bity), analogicznie argument ```y``` jest przekazywany przez %rdx i %rcx, a wynik jest zwracany w rejestrach %rdx i %rax.
Wskazówka! Użyj instrukcji ```adc```. Rozwiązanie wzorcowe składa się z 4 instrukcji bez ```ret```.
:::
| | Starsze bity | Mlodsze bity |
| --- | ------------ | ------------ |
| x | %rdi | %rsi |
| y | %rdx | %rcx |
| ret | %rdx | %rax |
ADD:
"It evaluates the result for both signed and unsigned integer operands and sets the CF and OF flags to indicate a carry (overflow) in the signed or unsigned result, respectively. "
```
add128:
#dodajemy mlodsze bity
addq %rsi, %rcx #flaga CF trzyma overflow z dodawania
#dodajemy starsze bity + flage CF dzieki adc (DEST <- DEST + SRC + CF)
adcq %rdi, %rdx #rdx trzyma sume starszych bitow tak jak w poleceniu
movq %rcx, %rax #przenosimy sume mlodszych bitow do rax
ret
```
### Zadanie 6
:::info
Zaimplementuj w asemblerze x86-64 funkcję liczącą wyrażenie ```x * y```. Argumenty i wynik funkcji są 128-bitowymi liczbami całkowitymi bez znaku. Argumenty i wynik są przypisane do tych samych rejestrów co w poprzednim zadaniu. Instrukcja ```mul``` wykonuje co najwyżej mnożenie dwóch 64-bitowych liczb i zwraca 128-bitowy wynik. Wiedząc, że n = n~127...64~ · 2^64^ + n~63...0~, zaprezentuj metodę obliczenia iloczynu, a dopiero potem przetłumacz algorytm na asembler.
UWAGA! Zapoznaj się z dokumentacją instrukcji ```mul``` ze względu na niejawne użycie rejestrów %rax i %rdx
:::
x - na rejestrach %rdi:%rsi
y - na rejestrach %rdx:%rcx
Wynik - na rejestrach %rdx:%rax
Wiemy, że każdą liczbę n 128-bitową można zapisać jako: $$n_{127...64} \cdot 2^{64} + n_{63...0}$$
Wobec tego $x*y = (x_{127...64}*y_{127...64}*2^{128}) + (x_{63...0}*y_{127...64}*2^{64}) + \\(y_{63...0}*x_{127...64}*2^{64}) +(x_{63...0}*y_{63...0})$
Wynik ma być na rejestrach %rdx:%rax (czyli 128 bitach), więc pierwszą część powyższej sumy, możemy pominąć (nie zmieści się).
Rozważamy więc jedynie 3 ostanie składniki powyższej sumy, ponieważ one mieszczą się na 128 bitach:
```
multiply:
movq %rdx,%r8
movq $0,%rdx
movq %rdi,%r9
imulq %rcx,%r9
imulq %rsi,%r8
movq %rsi,%rax
mul %rcx # w przypadku OF wynik będzie w %rdx:%rax,
addq %r9,%rdx
addq %r8,%rdx
```
### Zadanie 7
:::info
Zaimplementuj poniższą funkcję w asemblerze x86-64, przy czym wartości «x» i «y» typu ```uint64_t``` są przekazywane przez rejestry %rdi i %rsi, a wynik zwracany w rejestrze %rax. Po napisaniu rozwiązania uprość je z użyciem instrukcji ```set``` albo ```cmov``` albo ```sbb```.
$$
addu(x,y) =
\begin{cases}
\texttt{ULONG_MAX} & \text{dla } x + y \geq \texttt{ULONG_MAX} \\
x + y & \text{w p.p.}
\end{cases}
$$
:::
Główna idea jest taka, że chcemy sprawdzić czy dodawanie x i y nie wywołało nam overflow.
Używamy warunku below (mniejszy bez znaku) odpowiednio w skoku i cmov. Jeżeli składnik okaże się większy od wyniku to mamy overflow.
Wtedy przypisujemy %rax wartość ULONG_MAX.
wersja ze skokiem warunkowym:
```
addu:
leaq (%rdi, %rsi), %rax
cmp %rdi, %rax
jb label
ret
label:
movq $0xFFFFFFFF, %rax // ULONG_MAX = 0xFFFFFFFF
ret
```
wersja z użyciem set, cmov lub sbb:
```
addu:
leaq (%rdi,%rsi), %rax
cmp %rdi,%rax
cmovb $0xFFFFFFFF, %rax
ret
```
### Zadanie 8
:::info
W wyniku deasemblacji procedury ```long decode(long x, long y)``` otrzymano kod:
```
decode: leaq (%rdi,%rsi), %rax
xorq %rax, %rdi
xorq %rax, %rsi
movq %rdi, %rax
andq %rsi, %rax
shrq $63, %rax
ret
```
Zgodnie z System V ABI dla architektury x86-64, argumenty ```x``` i ```y``` są przekazywane odpowiednio przez rejestry %rdi i %rsi, a wynik zwracany w rejestrze %rax. Napisz funkcję w języku C, która będzie liczyła dokładnie to samo co powyższy kod w asemblerze. Postaraj się, aby była ona jak najbardziej zwięzła.
:::
- long x jest w rejestrze %rdi
- long y jest w rejestrze %rsi
- long z jest w rejestrze %rax
Tutaj funkcja niezwięzła
```c=
long decode(long x, long y)
{
long z = x + y;
x ^= z;
y ^= z;
z = x;
z &= y;
z = ((unsigned long)z) >> 63;
return z;
}
```
Wersja skrócona
```c=
long decode(long x, long y)
{
long z = x + y;
return (((unsigned long)((x^z) & (y^z))) >> 63);
}
```
Funkcja sprawdza czy podczas dodawania liczb ze znakiem doszło do przepełnienia.
### Zadanie 9
:::info
Zapisz w języku C funkcję o sygnaturze ```int puzzle(long x, unsigned n)``` której kod w asemblerze podano niżej. Zakładamy, że parametr ```n``` jest niewiększy niż 64. Przedstaw jednym zdaniem
co robi ta procedura.
```
puzzle: testl %esi, %esi
je .L4
xorl %edx, %edx
xorl %eax, %eax
.L3: movl %edi, %ecx
andl $1, %ecx
addl %ecx, %eax
sarq %rdi
incl %edx
cmpl %edx, %esi
jne .L3
ret
.L4: movl %esi, %eax
ret
```
:::
- %rdi - x
- %edi - n
- %eax - nasz wynik (cnt)
- %edx - zmienna pomocnicza do petli (y)
```c=
int puzzle(long x , unsigned int n)
{
int cnt=0;
for(int y=0;y!=n;y++)
{
cnt+=(x&1);
x>>=1;
}
return cnt;
}
```
Dana procedura liczy ile jest zapalonych bitów wśród n najmniej znaczących bitów liczby x.
# Lista 5 (09.04.2020)
### Zadanie 1
:::info
Zaimplementuj funkcję zdefiniowaną poniżej w asemblerze ```x86-64```. Taka procedura w języku ```C``` miałaby sygnaturę ```long cmp(uint64_t x, uint64_t y)```.
$$
cmp(x,y) =
\begin{cases}
-1 & \text{gdy } x < y \\
\phantom{+}1 & \text{gdy } x > y \\
\phantom{+}0 & \text{gdy } x = y \\
\end{cases}
$$
Wskazówka: Rozwiązanie wzorcowe ma cztery wiersze (bez ```ret```). Użyj instrukcji ```adc```, ```sbb``` i ```neg```.
:::
```
neg D - arithmetic negation (D = -D) (modyfikuje CF)
sbb S, D - substract with borrow (D = D - (S +CF)) (modyfikuje CF)
adc S, D - add with carry (D = D + S + CF) (modyfikuje CF)
```
```
Parametry funkcji:
x -> %rdi
y -> %rsi
Wynik zwracamy w %rax
```
```scala=
cmp:
subq %rsi, %rdi // x - y; CF = 1 dla x < y
sbbq %rax, %rax // rax = -1 dla x < y
// wpp. rax = 0
negq %rdi // x = 0 - (x-y);
// CF = 1 dla x > y lub x < y
adcq %rax, %rax // x < y : rax = -1
// x == y : rax = 0
// x > y : rax = 1
ret
```
### Zadanie 2
:::info
Poniżej zamieszczono kod procedury o sygnaturze «long puzzle2(char *s, char *d)». Wyznacz bloki podstawowe oraz narysuj graf przepływu sterowania. Przetłumacz tę procedurę na język C, a następnie jednym zdaniem powiedz co ona robi.
```=
puzzle2:
movq %rdi, %rax
.L3:
movb (%rax), %r9b
leaq 1(%rax), %r8
movq %rsi, %rdx
.L2:
movb (%rdx), %cl
incq %rdx
testb %cl, %cl
je .L4
cmpb %cl, %r9b
jne .L2
movq %r8, %rax
jmp .L3
.L4:
subq %rdi, %rax
ret
```
:::
Zauważmy, że s i d to są tablice typu char. Nasza procedura iteruje się po tablicy s, sprawdzając po kolei, czy jej elementy należą również do d. Jeśli napotka element z s , który nie należy do d, procedura kończy działanie i zwraca liczbę pierwszych elementów z s należących do d.
Tabelka zmiennych dla poniższego kodu:
|Zmienna |Rejestr |
|-----------|-----------|
|s |%rdi |
|d |%rsi |
|temp_s |%rax |
|temp_d |%rdx |
|s_znak |%r9b |
|d_znak |%cl |
|s_next |%r8 |
Wersja z goto:
```C=
long puzzle2_goto(char* s,char* d)
{
char* temp_s = s; char s_znak,d_znak; char* s_next; char* temp_d; // 1
L3:
s_znak = *temp_s;// 2
s_next = temp_s+1;
temp_d = d;
L2:
d_znak = *temp_d; // 3
temp_d = temp_d+1;
if (d_znak == 0) return temp_s-s; // end - sprawdzamy czy koniec tablicy d
if (d_znak != s_znak) goto L2; //4
else{ // 5
temp_s = s_next;
goto L3;
}
}
```
Dla przypomnienia:
- blok podstawowy - grupa instrukcji zakończona instrukcją skoku
- graf przepływu sterowania - graf skierowany, w którym wierzchołkami są bloki podstawowe - reprezentuje możliwe ścieżki działanie programu
Graf przepływu sterowania (bloki podstawowe oznaczone w komentarzach powyżej):
```graphviz
digraph graf{
6 [label="end"]
1->2
2->3
3->4
3->6
4->5
4->3
5->2
}
```
Kod w C bez goto:
```C=
long puzzle2_normal(char *s,char *d)
{
char* temp_s=s;
char* temp_d;
while (1)
{
temp_d = d;
while (*temp_d != *temp_s)
{
if (*temp_d == 0) return temp_s-s;
temp_d++;
}
temp_s++;
}
}
```
### Zadanie 3
:::info
Poniżej widnieje kod funkcji o sygnaturze «uint32_t puzzle3(uint32_t n, uint32_t d)». Wyznacz bloki podstawowe oraz narysuj graf przepływu sterowania, po czym przetłumacz tę funkcję na język C. Na podstawie ustępu „Mixing C and Assembly Language” strony GNU Assembler Examples3 napisz program, który pomoże Ci powiedzieć co ta funkcja robi.
```=
puzzle3:
movl %edi, %edi
salq $32, %rsi
movl $32, %edx
movl $0x80000000, %ecx
xorl %eax, %eax
.L3:
addq %rdi, %rdi
movq %rdi, %r8
subq %rsi, %r8
js .L2
orl %ecx, %eax
movq %r8, %rdi
.L2:
shrl %ecx
decl %edx
jne .L3
ret
```
:::
```=
.global _start
.global puzzle3
.text
puzzle3:
movl %edi, %edi //A
salq $32, %rsi
movl $32, %edx
movl $0x80000000, %ecx
xorl %eax, %eax
.L3: addq %rdi, %rdi //B
movq %rdi, %r8
subq %rsi, %r8
js .L2
orl %ecx, %eax //C
movq %r8, %rdi
.L2: shrl %ecx //D
decl %edx
jne .L3
ret
_start:
call puzzle3
syscall
```
```graphviz
digraph finite_state_machine {
node [shape=box]
START -> A
A -> B
B -> C
C -> D
D -> STOP
edge [style=dashed]
B -> D
D -> B
}
```
```
Parametry funkcji:
n -> %edi
d -> %esi
Zmienne:
x -> %rsi
m -> %rdi
a -> %edx
b -> %ecx
result -> %eax
```
```C=
uint32_t puzzle3(uint32_t n, uint32_t d)
{
int64_t x = d << 32;
int32_t a = 32;
int64_t b = 0x80000000;
int32_t result = 0;
int64_t m = n;
while(a>=0)
{
m*=2;
if (m - x >= 0)
{
result = b | result;
m -= x;
}
b /= 2;
a -= 1;
}
return result;
}
```
Ten algorytm to restoring division (n przez d).
### Zadanie 4
:::info
Poniżej zamieszczono kod rekurencyjnej procedury o sygnaturze «int puzzle4(long *a, long v, uint64_t s, uint64_t e)». Wyznacz bloki podstawowe oraz narysuj graf przepływu sterowania. Przetłumacz tę procedurę na język C, a następnie jednym zdaniem powiedz co ona robi.
```=
puzzle4:
movq %rcx, %rax
subq %rdx, %rax
shrq %rax
addq %rdx, %rax
cmpq %rdx, %rcx
jb .L5
movq (%rdi,%rax,8), %r8
cmpq %rsi, %r8
je .L10
cmpq %rsi, %r8
jg .L11
leaq 1(%rax), %rdx
call puzzle4
.L10:
ret
.L11:
leaq -1(%rax), %rcx
call puzzle4
ret
.L5:
movl $-1, %eax
ret
```
Wskazówka: Z reguły procedurę «puzzle4» woła się następująco: «i = puzzle4(a, v, 0, n - 1)»
:::
```
puzzle4:
movq %rcx, %rax //1
subq %rdx, %rax
shrq %rax
addq %rdx, %rax
cmpq %rdx, %rcx
jb .L5
movq (%rdi,%rax,8), %r8 // 2
cmpq %rsi, %r8
je .L10
cmpq %rsi, %r8 // 3
jg .L11
leaq 1(%rax), %rdx // 4
call puzzle4
.L10: ret // 5
.L11: leaq -1(%rax), %rcx // 6
call puzzle4
ret
.L5: movl $-1, %eax // 7
ret
```
```graphviz
digraph finite_state_machine {
START -> 1
1 -> 2
2 -> 3
3 -> 4
4 -> 5
5 -> STOP
6 -> STOP
7 -> STOP
1 -> 7
2 -> 5
3 -> 6
}
```
puzzle4: BinarySearch
s (początek tablicy)- rdx
e (koniec tablicy)- rcx
a (tablica)- rdi
v (szukana wartość)- rsi
```c=
int puzzle4(long *a,long v, uint64_t s, uint64_t e)
{
if (e >= s) {
int mid = s + (e - s) / 2;
if (a[mid] == v)
return mid;
if (a[mid] > v)
return puzzle4(a, v, s, mid - 1);
return puzzle4(a, v, mid + 1, e);
}
return -1;
}
```
### Zadanie 5
:::info
Poniższy kod w asemblerze otrzymano w wyniku deasemblacji funkcji zadeklarowanej jako «long switch_prob(long x, long n)». Zapisz w języku C kod odpowiadający tej funkcji.
```
400590 <switch_prob>:
400590: 48 83 subq $0x3c,%rsi
400594: 48 83 fe 05 cmpq $0x5,%rsi
400598: 77 29 ja *0x4005c3
40059a: ff 24 f5 f8 06 40 00 jmpq *0x4006f8(,%rsi,8)
4005a1: 48 8d 04 fd 00 00 00 00 lea 0x0(,%rdi,8),%rax
4005a9: c3 retq
4005aa: 48 89 f8 movq %rdi,%rax
4005ad: 48 c1 f8 03 sarq $0x3,%rax
4005b1: c3 retq
4005b2: 48 89 f8 movq %rdi,%rax
4005b5: 48 c1 e0 04 shlq $0x4,%rax
4005b9: 48 29 f8 subq %rdi,%rax
4005bc: 48 89 c7 movq %rax,%rdi
4005bf: 48 0f af ff imulq %rdi,%rdi
4005c3: 48 8d 47 4b leaq 0x4b(%rdi),%rax
4005c7: c3 retq
```
Zrzut pamięci przechowującej tablicę skoków:
```
(gdb) x/6gx 0x4006f8
0x4006f8: 0x4005a1
0x400700: 0x4005a1
0x400708: 0x4005b2
0x400710: 0x4005c3
0x400718: 0x4005aa
0x400720: 0x4005bf
```
:::
```c=
long switch_prob(long x, long n){
n-=0x3c;
switch(n){
case 0:
case 1:
return 8*x;
case 4:
return x<<3;
case 2:
x*=15;
case 5:
x*=x;
case 3:
default:
return x+0x4b;
}
}
```
### Zadanie 6
:::info
Poniżej zamieszczono kod procedury o sygnaturze «struct T puzzle8(long *a, long n)». Na jego podstawie podaj definicję typu «struct T». Przetłumacz tę procedurę na język C, po czym jednym zdaniem powiedz co ona robi. Gdyby sygnatura procedury nie była wcześniej znana to jaką należałoby wywnioskować z poniższego kodu?
```=
puzzle8:
movq %rdx, %r11
xorl %r10d, %r10d
xorl %eax, %eax
movq $LONG_MIN, %r8
movq $LONG_MAX, %r9
.L2:
cmpq %r11, %r10
jge .L5
movq (%rsi,%r10,8), %rcx
cmpq %rcx, %r9
cmovg %rcx, %r9
cmpq %rcx, %r8
cmovl %rcx, %r8
addq %rcx, %rax
incq %r10
jmp .L2
.L5:
cqto
movq %r9, (%rdi)
idivq %r11
movq %r8, 8(%rdi)
movq %rax, 16(%rdi)
movq %rdi, %rax
ret
```
Wskazówka: Zauważ, że wynik procedury nie mieści się w rejestrach %rax i %rdx, zatem zostanie umieszczony w pamięci.
:::
```
Argumenty funkcji:
struct *T -> %rdi (wskaźnik na miejsce na strukturę)
long *a -> %rsi (wskaźnik na tablicę)
long n -> %rdx (długość tablicy)
Zmienne:
int i -> %r10d
long sum -> %rax
long max -> %r8
long min -> %r9
long value -> &rcx
long average -> %r11
struct *T result -> %rax (zwracany wskaźnik na miejsce w pamięci, w którym
zapisaliśmy wynik)
```
Warto zauważyć, że sum zostaje zapisana do %rax.
Dzieje się tak, gdyż kilka linii później wywołujemy instrukcję idiv, która
aby wykonać dzielenie korzysta właśnie z tego rejestru.
```
cqto - instrukcja rozszerza %rax do %rdx:%rax (tak, że wartość
z %rax zachowuje znak)
idivq S - dzielenie ze znakiem %rdx : %rax przez S, gdzie
%rdx - wynik dzielenia całkowitego
%rax - reszta z dzielenia
```
Widzimy również, że wyniki z naszej funkcji (max, min, sum) zapisujemy
pod adresem przechowanym w %rdi przesuniętym o odpowiednią ilość bajtów,
tak, by adres odpowiadał konkretnemu polu w strukturze. Nie przekazaliśmy
tego adresu jawnie jako parametr w funkcji, ale dostaliśmy go wraz z jej wywołaniem.
(Chcąc wywołać tą funkcję w assemblerze musielibyśmy najpierw zadbać o to,
by dostała wskaźnik na miejsce w pamięci w której ma zapisać utworzoną strukturę.)
```c=
struct T
{
long min;
long max;
long average;
}
struct T puzzle8(long *a, long n)
{
long sum = 0; //4
long max = LONG_MIN; //5 (r8)
long min = LONG_MAX; //6 (r9)
for(int i = 0; i < n; i++) //3, 7, 8, 16
{
long value = a[i]; //10
if(min > value) //11 (r9 > val)
min = value; //12
if(max < value) //13 (r8 < val)
max = value; //14
sum += value; //15
} //17
//19 cqto - przygotowuje rdx pod dzielenie
long average = sum/n; //2, 21
struct T result;
result.min = min; //20
result.max = max; //22
result.average = average; //23
return result; //25
}
```
Wnioskowanie dotyczące sygnatury: linie 20-22 wskazują, że zapisujemy do miejsc wskazanych przez %rdi i offset, a zwracamy rdi. Argumentami funkcji jest wartosc n (linie 2 i 19) i adres wskazany przez a (linia 9).
Możliwe by było wywnioskowanie sygnatury:
```
void* puzzle8(struct* T result, long *a, long n )
```
gdzie wynikiem jest result.
Zatem chociaż w kodzie "zwracamy strukturę" to w rzeczywistości, ponieważ nie mieści się na rejestrze %rdx:%rax, zapisujemy ją w pamięci i zwracamy wskaźnik na nią.
Jeśli chodzi o działanie funkcji to przyjmuje ona tablicę ```a``` typu ```long``` oraz jej długość ```n```, zaś zwraca strukturę przechowującą informacje o liczbie najmniejszej, największej i średniej wszystkich elementów.
|Dla poniższych zadań należy podać kompletny algorytm, zatem dozwolona jest cała składnia języka C bez ograniczeń z nagłówka listy zadań. Jednakże należy używać wyłącznie operacji na typie «int32_t» lub «uint32_t».|
|-|
### Zadanie 7 z listy 3
:::info
Uzupełnij ciało poniższej procedury konwertującej wartość całkowitoliczbową do binarnej reprezentacji liczby typu «float». Wynik należy zaokrąglić do najbliższej liczby parzystej – w tym celuwyznacz wartość bitów guard, round i sticky. Do wyznaczenia pozycji wiodącej jedynki można użyć funkcji «__builtin_clz», tj. instrukcji wbudowanej1 w kompilator gcc.
```cpp
int32_t int2float(int32_t i) {
/* TODO */
}
```
:::
```c=
int32_t int2float(int32_t i) {
/*
przykład: int32_t = 00000000000000000000000101011101 =
= 2^8 + 2^6 + 2^4 + 2^3 + 2^2 + 2^0 = 2^8 (1 + 2^-2 + 2^-4 + 2^-5 + 2^-6 + 2^-8) =
= 2^8 * 1.01011101
czyli:
e = 8 + 127 (trzeba uwzględnić bias)
m = 1.01011101
s = 0
kolejny przykład -127 = -1 * (2^6 + 2^5 + 2^4 + 2^3 + 2^2 + 2^1 + 2^0) =
= -1 * 1.111111 * 2^6 (czyli przesunięcie bitowe o 6 w lewo)
*/
/* znak - sign:
-1 jeśli znak był ujemny */
int32_t s = i >> 31;
/* jesli i jest ujemna to otrzymaliśmy już znak,
a chcemy znaleźć wiodącą jedynkę, która nie oznacza znaku */
if (s == -1) {
i = -i; // usuwamy jedynkę z początkowego bitu
s = 1;
}
/* pozycja:
__builtin_clz zwraca liczbę zer wiodących
więc mozemy policzyć pozycje wiodącej jedynki (od prawej)
odejmując od 32 wynik __builtin_clz(i) */
int32_t p = 32 - __builtin_clz(i);
/* wykładnik - exponent:
po odjęciu 1 (bo chcemy numerować bity od 0, by i-ty bit oznaczał 2^i w sumie)
i dodaniu bias (obliczając wykładnik trzeba go odejmować)
mamy nasz wykładnik */
int32_t bias = 127;
int32_t e = p - 1 + bias;
/* tworzymy maskę, aby wyeliminować wiodącą jedynkę, aby mieć tylko mantysę
więc przesuwamy 1 na pozycję p i wtedy odejmujemy 1, aby na prawo od p były same 1*/
int32_t mask = (1<<(p-1))-1;
/* eliminujemy wiodącą jedynkę */
i &= mask;
/* guard, round i sticky
potrzebne nam będą do zaokrąglenia round-to-even */
int32_t
guard = 0,
round = 0,
sticky = 0;
/* zaokrąglamy, gdy pozycja p jest większa niż 23,
ponieważ na prawo od niej znajduje się wtedy za dużo bitów na mantysę */
if (p > 24) {
/* ostatni bit mantysy jest na pozycji 23 na prawo od p ( p - 23 )
więc jeśli chcemy dostać się do tego bitu to przesuniemy o (p - 23) - 1
bo aby dostać się do bitu k-tego na prawo, przesuwamy o k-1 w prawo */
guard = ( i >> (p - 23 - 1) ) & 1;
/* bit round jest o 1 w prawo od bitu guard */
round = ( i >> (p - 23 - 2) ) & 1;
/* sticky bit to OR bitów na prawo od round
więc 1 przesuwamy na pozycję round i odejmujemy 1
więc aby wziąć OR, wystarczy sprawdzić czy liczba jest większa od 0*/
sticky = ( i & ( ( 1 << (p - 23 - 2) ) - 1 ) ) > 0;
}
/* mantysa - fraction:
chcemy, aby miejsce wiodącej jedynki znajdowało się na 24 pozycji od prawej,
aby na najmniej znaczących 23 bitach znajdowała się mantysa
musimy dosunąć
w lewo o tyle ile brakuje nam do 24 pozycji (jeśli p < 24)
w prawo o tyle ile jesteśmy za daleko do 24 pozycji (jeśli p >= 24) */
int32_t f =
( p < 24 ? (i << (24 - p)) : (i >> (p - 24)) ) +
((guard & round) | (round & sticky)) ;
return (s << 31) + (e << 23) + f ;
}
```
### Zadanie 8 z listy 3
:::info
Uzupełnij ciało poniższej procedury konwertującej binarną reprezentację liczby typu «float» umieszczoną w zmiennej «f» do wartości typu «int32_t». Wynik należy zaokrąglić w kierunku zera. Jeśli konwersja spowoduje nadmiar lub f ma wartość NaN, zwróć wartość 0x80000000 (tj. MIN_INT). Kod procedury zaczyna się wyodrębnieniem poszczególnych elementów liczby zmiennopozycyjnej:
```cpp
int32_t float2int(int32_t f) {
int32_t s = f >> 31; /* -1 jeśli znak był ujemny */
int32_t e = (f >> 23 & 255) - 127; /* wykładnik po odjęciu bias */
uint32_t m = f << 8 | 0x80000000; /* mantysa 1.xxx... dosunięta do lewej */
/* TODO */
}
```
Wskazówka: Wzorcówka ma dodatkowe cztery linie kodu i używa jednej instrukcji warunkowej!
:::
```cpp
int32_t float2int(int32_t f)
{
int32_t s = (f >> 31)|1; // -1 jeśli znak był ujemny
int32_t e = (f >> 23 & 255) - 127; // wykładnik po odjęciu bias
uint32_t m = f << 8 | 0x80000000; // mantysa 1.xxx... dosunięta do lewej
if(e > 30)
return 0x80000000; //dla Nan, dużych liczb lub MIN_INT
if(e < 0)
return 0; // dla małych liczb
// s*=(e>=0);
return s * (m >> (31-e));
}
```
# Lista 6 (16.04.2020)
### Zadanie 1
:::info
Poniższy wydruk otrzymano w wyniku deasemblacji rekurencyjnej procedury zadeklarowanej następująco: «long puzzle(long n, long *p)». Zapisz w języku C kod odpowiadający tej procedurze. Następnie opisz zawartość jej rekordu aktywacji (ang. stack frame). Wskaż rejestry zapisane przez funkcję wołaną (ang. callee-saved registers), zmienne lokalne i adres powrotu.
```=
puzzle:
push %rbp
xorl %eax, %eax
mov %rsi, %rbp
push %rbx
mov %rdi, %rbx
sub $24, %rsp
test %rdi, %rdi
jle .L1
lea 8(%rsp), %rsi
lea (%rdi,%rdi), %rdi
call puzzle
add 8(%rsp), %rax
add %rax, %rbx
.L1:
mov %rbx, (%rbp)
add $24, %rsp
pop %rbx
pop %rbp
ret
```
Uwaga! Wskaźnik wierzchołka stosu w momencie wołania procedury musi być wyrównany do adresu podzielnego przez 16.
:::
```c=
long puzzle(long n,long* p)
{
long res=0;//%rax
if(n>0)
{
res=puzzle(n*2,p);
res+=*p;
n+=res;
}
*p=n;
return res;
}
```
| rekord aktywacji |
| -------------------------------------------------- |
| %rbp |
| %rbx |
| - |
| na to miejsce wskazuje %rsi gdy wywolujemy funkcje |
| - |
| call puzzle |
rejestry zapisane przez funkcje wołaną: %rbp, %rbx
zmienne lokalne = res -> %rax
adres powrotu jest w %rbp przy wywołaniu funkcji odkładamy go na stos,
przy powrocie jest zdejmowany (instrukcje push i pop)
### Zadanie 2
:::info
Skompiluj poniższy kod źródłowy kompilatorem gcc z opcjami «-Og -fomit-frame-pointer» i wykonaj deasemblację jednostki translacji przy użyciu programu «objdump». Wytłumacz co robi procedura alloca(3), a następnie wskaż w kodzie maszynowym instrukcje realizujące przydział i zwalnianie pamięci.
```cpp=
#include <alloca.h>
long aframe(long n, long idx, long *q) {
long i;
long **p = alloca(n * sizeof(long *));
p[n-1] = &i;
for (i = 0; i < n; i++)
p[i] = q;
return *p[idx];
}
```
Wskazówka: Przeczytaj również co robi instrukcja «leave».
:::
```c=
000000000000066a <aframe>:
66a: 55 push %rbp
66b: 48 89 e5 mov %rsp,%rbp
//przydział pamięci na stosie - początek
66e: 48 83 ec 10 sub $0x10,%rsp
672: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
679: 00 00
67b: 48 89 45 f8 mov %rax,-0x8(%rbp)
//w rax obliczamy ile pamięci potrzebujemy na n*long
67f: 31 c0 xor %eax,%eax
681: 4c 8d 0c fd 00 00 00 lea 0x0(,%rdi,8),%r9
688: 00
689: 49 8d 41 1e lea 0x1e(%r9),%rax
68d: 48 83 e0 f0 and $0xfffffffffffffff0,%rax
691: 48 29 c4 sub %rax,%rsp
//przydział pamięci na stosie - koniec
694: 4c 8d 44 24 0f lea 0xf(%rsp),%r8
699: 49 83 e0 f0 and $0xfffffffffffffff0,%r8
69d: 4c 89 c1 mov %r8,%rcx
6a0: 48 8d 45 f0 lea -0x10(%rbp),%rax
6a4: 4b 89 44 08 f8 mov %rax,-0x8(%r8,%r9,1)
6a9: 48 c7 45 f0 00 00 00 movq $0x0,-0x10(%rbp)
6b0: 00
6b1: eb 09 jmp 6bc <aframe+0x52>
6b3: 48 89 14 c1 mov %rdx,(%rcx,%rax,8)
6b7: 48 83 45 f0 01 addq $0x1,-0x10(%rbp)
6bc: 48 8b 45 f0 mov -0x10(%rbp),%rax
6c0: 48 39 f8 cmp %rdi,%rax
6c3: 7c ee jl 6b3 <aframe+0x49>
6c5: 49 8b 04 f0 mov (%r8,%rsi,8),%rax
6c9: 48 8b 00 mov (%rax),%rax
6cc: 48 8b 75 f8 mov -0x8(%rbp),%rsi
6d0: 64 48 33 34 25 28 00 xor %fs:0x28,%rsi
6d7: 00 00
6d9: 75 02 jne 6dd <aframe+0x73>
//zwolnienie pamięci na stosie
6db: c9 leaveq
6dc: c3 retq
6dd: e8 5e fe ff ff callq 540 <__stack_chk_fail@plt>
```
alloca - funkcja alokująca pamięć, która jest automatycznie zwalniana, kiedy funkcja, która wywołała alloca wraca do swojego callera(wywoływacza?).
leave zamyka ramkę. Jest równoważne skopiowaniu %rbp do %rsp, a następnie przywróceniu starej wartości %rbp ze stosu.
### Zadanie 3
:::info
Poniżej widnieje kod procedury o sygnaturze «long puzzle5(void)». Podaj rozmiar i składowe rekordu aktywacji procedury «puzzle5». Procedura «readlong», która wczytuje ze standardowego wejścia liczbę całkowitą, została zdefiniowana w innej jednostce translacji. Jaka jest jej sygnatura? Przetłumacz procedurę «puzzle5» na język C i wytłumacz jednym zdaniem co ona robi.
```=
puzzle5:
subq $24, %rsp
movq %rsp, %rdi
call readlong
leaq 8(%rsp), %rdi
call readlong
movq (%rsp), %rax
cqto
idivq 8(%rsp)
xorl %eax, %eax
testq %rdx, %rdx
sete %al
addq $24, %rsp
ret
```
:::
```c=
void readlong(long* a){
scanf("%l", &a);
}
int puzzle5() {
long a;
readlong(&a);
long b;
readlong(&b);
if(a%b == 0)
return 0;
return 1;
}
```
procedura puzzle5 mówi nam czy występuje reszta z dzielenia dwóch long'ów wczytanych w trakcie wykonania procedury (~ (b|a) )
rozmiar rekordu aktywacji - 32 bajty
trzymamy w nim zmienne a i b
### Zadanie 4
:::info
Procedurę ze zmienną liczbą parametrów używającą pliku nagłówkowego ```stdarg.h``` skompilowano z opcjami «-Og -mno-sse». Po jej deasemblacji otrzymano następujący wydruk. Co robi ta procedura i jaka jest jej sygnatura? Jakie dane są przechowywane w rekordzie aktywacji tej procedury? Prezentację zacznij od przedstawienia definicji struktury «va_list».
```=
puzzle7:
movq %rsi, -40(%rsp)
movq %rdx, -32(%rsp)
movq %rcx, -24(%rsp)
movq %r8, -16(%rsp)
movq %r9, -8(%rsp)
movl $8, -72(%rsp)
leaq 8(%rsp), %rax
movq %rax, -64(%rsp)
leaq -48(%rsp), %rax
movq %rax, -56(%rsp)
movl $0, %eax
jmp .L2
.L3:
movq -64(%rsp), %rdx
leaq 8(%rdx), %rcx
movq %rcx, -64(%rsp)
.L4:
addq (%rdx), %rax
.L2:
subq $1, %rdi
js .L6
cmpl $47, -72(%rsp)
ja .L3
movl -72(%rsp), %edx
addq -56(%rsp), %rdx
addl $8, -72(%rsp)
jmp .L4
.L6:
ret
```
Wskazówka: Przeczytaj rozdział §3.5.7 dokumentu opisującego ABI dostępnego na stronie przedmiotu.
:::
Definicja struktury va_list:
```C=
typedef struct{
unsigned int gp_offset; /* "odległość" od reg_save_area
do kolejnego argumentu przekazanego przez zwykły rejestr*/
unsigned int fp_offset; /* "odległość" od reg_save_area
do kolejnego rejestru typu %xmm0...15
(rejestry dla liczb zmiennoprzecinkowych )*/
void *overflow_arg_area;/* używany do pobierania argumentów
przekazanych przez stos*/
void *reg_save_area; /* początek miejsca, gdzie mamy argumenty
przekazane przez rejestry */
}
```
W naszej procedurze:
- gp_offset: -72(%rsp)
- overflow_arg_area: 8(%rsp)
- reg_save_area: -48(%rsp)
- fp_offset: kompilowaliśmy z flagą -mno-sse, dzięki czemu
wszystkie argumenty przekazane zostały przez rejestry ogólnego przeznaczenia albo stos. W tej procedurze fp_offset nie jest wyodrębniony.
--------------------------
```=
puzzle7:
movq %rsi, -40(%rsp)
movq %rdx, -32(%rsp)
movq %rcx, -24(%rsp)
movq %r8, -16(%rsp)
movq %r9, -8(%rsp)
movl $8, -72(%rsp)
leaq 8(%rsp), %rax
movq %rax, -64(%rsp)
leaq -48(%rsp), %rax
movq %rax, -56(%rsp) # Koniec przygotowania stosu
movl $0, %eax
jmp .L2
.L3:
movq -64(%rsp), %rdx # Czytamy kolejny argument przekazany
leaq 8(%rdx), %rcx # przez stos
movq %rcx, -64(%rsp)
.L4:
addq (%rdx), %rax # w %rdx mamy obliczony adres kolejnego
.L2: # argumentu funkcji. Dodajemy go do %rax
subq $1, %rdi
js .L6
cmpl $47, -72(%rsp) # Sprawdzamy, czy jeszcze możemy
ja .L3 # korzystać z argumentów z rejestrów
movl -72(%rsp), %edx # Czytamy kolejny argument przekazany przez rejestr
addq -56(%rsp), %rdx
addl $8, -72(%rsp)
jmp .L4
.L6:
ret
```
Stan stosu po wykonaniu początkowych instrukcji (lin. 11):
| ADRES | ZAWARTOŚĆ |
| --------- | -------------------------------------- |
| 32(%rsp) | ... |
| 24(%rsp) | arg3 |
| 16(%rsp) | arg2 |
| 8(%rsp) | arg1 |
| %rsp | |
| -8(%rsp) | r9 |
| -16(%rsp) | r8 |
| -24(%rsp) | rcx |
| -32(%rsp) | rdx |
| -40(%rsp) | rsi |
| -48(%rsp) | |
| -56(%rsp) | reg_save_area, początkowo: -48(%rsp) |
| -64(%rsp) | overflow_arg_area, pocz: 8(%rsp) |
| -72(%rsp) | gp_offset, pocz: 8 |
| -80(%rsp) | ... |
- rsi,rdx...r9 - argumenty funkcji przekazane przez rejestry
- arg1,arg2... - argumenty funkcji przekazane przez stos
----------------------
W naszej procedurze czytamy kolejne argumenty funkcji (przekazane przez rejestr albo stos) i dodajemy do %rax, przy czym pierwszy argument funkcji (%rdi) to liczba składników (kolejnych argumentów funkcji). Nasza funkcja ma więc sygnaturę:
```C=
long add_args(long n, ...)
```
# Lista 7 (23.04.2020)
### Zadanie 1
:::info
Przeczytaj poniższy kod w języku C i odpowiadający mu kod w asemblerze, a następnie wywnioskuj jakie są wartości stałych «A» i «B».
```cpp=
typedef struct {
int x[A][B];
long y;
} str1;
typedef struct {
char array[B];
int t;
short s[A];
long u;
} str2;
void set_val(str1 *p, str2 *q) {
long v1 = q->t;
long v2 = q->u;
p->y = v1 + v2;
}
```
```=
set_val:
movslq 8(%rsi),%rax
addq 32(%rsi),%rax
movq %rax,184(%rdi)
ret
```
:::
1. long v1=q->t == *q+8
2. long v2=q->u == *q+32
3. p->y == rdi+184
:::info
```cpp=
typedef struct {
char array[B]; // 8 //
int t; // 32
short s[A]; //
long u;
} str2;
```
Więc aby przejść do int t musimy się przesunąc o 8 bajtów,czyli char array[B] wynosi maksymalnie 8 bajtów.
Minimalny rozmiar char array[B] to 5,gdyż inaczej int mogłby ustawić się na wcześniejszej pozycji równej 4.
Char jest 1 bajtowy, więc 5<=B<=8.
Aby przejść do long u to przesuwamy się o 32 bajty.
32- 8(char array)- 4 (int t)=20
Więc short s[A] ma maksymalnie 20 bajtów, zaś minimalnie 13, gdyż 32-8 (tyle ile long mogłby się przesunąć) +1 (aby przesuniecie nie było możliwe)- 8 -4=13
Short ma 2 bity, więc 7<=A<=10
Maksymalna wielkość int x[A][B] wynosi 184 bajtów.
Minimalna to 184-8(wielkość longa)+1=177.
Ilość komórek to 177/4=45(zaokrąglone w góre) < x <184/4=46
A * B=45 lub A * B=46
5<=B<=8 7<=A<=10
Z obliczeń wynika,że A=5 a B=9.
:::
### Zadanie 2
:::info
Przeczytaj poniższy kod w języku C i odpowiadający mu kod w asemblerze, a następnie wywnioskuj jakie są wartości stałych «R», «S» i «T».
```cpp=
long A[R][S][T];
long store_elem(long i, long j, long k, long *dest)
{
*dest = A[i][j][k];
return sizeof(A);
}
```
```=
store_elem:
leaq (%rsi,%rsi,2),%rax //rax = 3j
leaq (%rsi,%rax,4),%rax //rax = 13j
movq %rdi,%rsi //rsi = i
salq $6,%rsi //rsi = i<< 6 = 64i
addq %rsi,%rdi //rdi = i+ 64i = 65i
addq %rax,%rdi //rdi = 13j + 65i
addq %rdi,%rdx //rdx = 13j + 65i + k
movq A(,%rdx,8),%rax //rax = A + 8(65i + 13j + k)
movq %rax,(%rcx) //*dest = A[i][j][k]
movq $3640,%rax //sizeof(A) = 8 * R * S * T = 3640
ret
```
:::
Szukamy wartości liczb R, S T. Widzimy, że sizeof(A) = 3640 oraz jest to tablica typu long, zatem mozemy wyliczyć, że
R * S * T = 3640/8 = 455.
Z wykładu wiemy, że chcać uzyskać element tablicy A[R][S][T] oznaczony A[i][j][k] musimy przesunąć wskaźnik A na miejsce
A + 8 * i * S * T + 8 * j * T + 8 * k = A + 8(i * S * T + j * T + k).
W kodzie chcemy odnieść się do pozycji A + 8(65i+ 13j + k), zatem widzimy, że T = 13,
a S = 65/13 = 5. Z naszego iloczynu R * S * T możemy wyliczyć, że R = 455/65 = 7.
Zatem:
T = 13
S = 5
R = 7
### Zadanie 3
:::info
Przeczytaj poniższy kod w języku C i odpowiadający mu kod w asemblerze, a następnie wywnioskuj jaka jest wartość stałej «CNT» i jak wygląda definicja struktury «a_struct».
```cpp=
typedef struct {
int first;
a_struct a[CNT];
int last;
} b_struct;
void test (long i, b_struct *bp) {
int n = bp->first + bp->last;
a_struct *ap = &bp->a[i];
ap->x[ap->idx] = n;
}
```
```=
test:
movl 0x120(%rsi),%ecx //0x120 = 288B, przesunięcie o tyle wskaźnika
addl (%rsi),%ecx
leaq (%rdi,%rdi,4),%rax
leaq (%rsi,%rax,8),%rax //obliczamy rozmiar struktury - 8*5 = 40B
movq 0x8(%rax),%rdx
movslq %ecx,%rcx
movq %rcx,0x10(%rax,%rdx,8)
retq
```
:::
```cpp=
typedef struct A
{
long idx;
long x[4];
}A;
```
CNT = 7
Przesuwamy wskaźnik o 288B, 8B rezerwujemy na int, więc zosteje nam 280B na struktury. Następnie chcąc dostać się do struktury musimy pomnożyć liczbę przez 40(obliczona przy pomocy poleceń leaq), stąd A posiada 40B, posiada też liczbę i tablicę - co wnioskujemy z funkcji void test w C, z których liczba jest na początku. Tablica jest tablicą longów, bo nasze n rozszerzamy na 64 bit. Long ma 64 bit(operujemy na rejestrach 64 bit), wieć jeśli chcemy mieć ich tablice będzie miała rozmiar 5 (40/8), jednak potrzbujemy miejsce na idx, więc ma rozmiar 4.
### Zadanie 4
:::info
Przeczytaj definicję unii «elem» i wyznacz jej rozmiar w bajtach. Następnie przepisz procedurę «proc» na kod w języku C.
```cpp=
union elem {
struct {
long *p;
long y;
} e1;
struct {
long x;
union elem *next;
} e2;
};
```
```=
proc:
movq 8(%rdi),%rax
movq (%rax),%rdx
movq (%rdx),%rdx
subq 8(%rax),%rdx
movq %rdx,(%rdi)
ret
```
:::
Unia w C ma zajmuje tyle pamięci co jej największy element.
Tutaj elementami są 2 structy. Każdy z tych structów zajmuje 16 bajtów zatem cała unia
również tyle zajmuje.
najpierw zapiszę wszystkie operacje bez interpretowania których structów dotyczą.
```cpp=
elem* proc(elem* now)
{
*now = *(*(*(now+8))) - *(*(now+8)+8);
return now->next;
}
```
teraz kod już po zinterpretowaniu
```cpp=
elem* proc(elem* now)
{
now->e2.x = *(now->e2.next->e1.p) - now->e2.next->e1.y;
return now->next;
}
```
wyjaśnienie:
zacznijmy od interpretacji tej części ```*(*(*(now+8)))```
```
(*(now+8))
```
to może być ```now->e2.next``` lub ```now->e1.y``` jednak w następnym kroku
chcę wykonać na tym elemencie dereferencje więc musi to być wskaźnik, zatem jest to ```now->e2.next```
```
(*(*(now+8))
```
jest to dereferencja wskaźnika na elem a wskaźnik na union elem jest jednocześnie wskaźnikiem na 1 element
tego uniona zatem może być to ```now->e2.next->e1.p``` lub ```now->e2.next->e2.x```. Znowu kolejnym
krokiem będzie dereferencja zatem znowu musi być to wskaźnik zatem mamy pewność że jest to
```now->e2.next->e1.p```
```
(*(*(*(now+8)))
```
dokonujemy dereferencji na wskaźniku na long zatem jest to ```*(now->e2.next->e1.p)```.
dane wyrażenie ma typ long. Można w takim razie wywnioskować już na tym etapie że to co chcemy
odjąć powinno mieć również typ long a także to do czego chcemy przypisać wartość różnicy również
powinno mieć typ long. Z tego widzimy od razy że ```*now``` ma być typu long zatem będzie to ```now->e2.x```
w takim razie intepretacja tej części ```*(*(now+8)+8)```
tak jak we wcześniejszym przykładzie na ```*(now+8)``` będę chciał dokonać w następnym kroku dereferencje
zatem musi być to wskaźnik więc będzie to ```now->e2.next```
wiemy z poprzedniej części rozwiązania że ```*(*(now+8)+8)``` powinno być longiem zatem będzie to
```now->e2.next->e1.y```
### Zadanie 5
:::info
Przeczytaj definicje struktur «SA» i «SB», po czym przeanalizuj kod procedur o sygnaturach «SB eval(SA s)» i «long wrap(long x, long y, long z)». Nastepnie zapisz w języku C kod odpowiadający procedurom «eval» i «wrap». Narysuj diagram przedstawiający zawartość rekordu aktywacji procedury «wrap» w momencie wywołania funkcji «eval».
```cpp=
typedef struct A {
long u[2];
long *v;
} SA;
typedef struct B {
long p[2];
long q;
} SB;
```
```=
eval:
movq %rdi, %rax
movq 16(%rsp), %rcx // bierzemy y ze stosu
movq 24(%rsp), %rdx // bierzemy &z ze stosu
movq (%rdx), %rsi // dokonujemy dereferencji
movq %rcx, %rdx
imulq %rsi, %rdx // y * z
movq %rdx, (%rdi) // kładziemy wynik na stosie
movq 8(%rsp), %rdx // bierzemy x ze stosu
movq %rdx, %rdi
subq %rsi, %rdi // x - z
movq %rdi, 8(%rax) // kładziemy wynik na stosie
subq %rcx, %rdx // x - y
movq %rdx, 16(%rax) // wynik kładziemy na stosie
ret
wrap:
subq $72, %rsp // szykujemy miejsce na stosie
movq %rdx, (%rsp) // kładziemy 'z' na stos
movq %rsp, %rdx
leaq 8(%rsp), %rax
pushq %rdx // kładziemy '&z' na stos
pushq %rsi // kładziemy 'y' na stos
pushq %rdi // kładziemy 'x' na stos
movq %rax, %rdi // jako argument dajemy adres "pod" zmiennymi na stosie
call eval
movq 40(%rsp), %rax // bierzemy (x - z)
addq 32(%rsp), %rax // dodajemy do niego (y * z)
imulq 48(%rsp), %rax // wynik mnożymy przez (x - y)
addq $96, %rsp // zwalniamy miejsce w stosie
ret
```
:::
W momencie wywołania eval
|------------------------|
| 96 | |
| 88 | |
| 80 | |
| 72 | |
| 64 | |
| 56 | |
| 48 | |
| 40 | |%rax, %rdi
| 32 | z |
| 24 | &z |v
| 16 | y |u[1]
| 8 | x |u[0]
| 0 |returnAdress |%rsp
Po zakończeniu eval
|------------------------|
| 96 | |
| 88 | |
| 80 | |
| 72 | |
| 64 | |
| 56 | x - y |q
| 48 | x - z |p[1]
| 40 | y * z |p[0]
| 32 | z |
| 24 | &z |
| 16 | y |
| 8 | x |%rsp
```c=
SB eval(SA sa)
{
SB sb;
sb.p[0] = sa.u[1] * (*sa.v);
sb.p[1] = sa.u[0] - (*sa.v);
sb.q = sa.u[0] - sa.u[1];
return sb;
}
long wrap(long x,long y,long z)
{
SA sa;
sa.v = &z;
sa.u[0] = x;
sa.u[1] = y;
SB sb = eval(sa);
return (sb.p[1] + sb.p[0]) * sb.q;
}
```
### Zadanie 6
:::info
Poniżej widniej kod procedury o sygnaturze «float puzzle6(struct P *, float)». Wyznacz definicję typu «struct P». Przetłumacz tę procedurę na język C i wyjaśnij jednym zdaniem co robi.
```=
puzzle6:
movq (%rdi), %rdx
leaq 8(%rdi), %rcx
xorl %eax, %eax
vxorps %xmm1, %xmm1, %xmm1
vmovss .LC1(%rip), %xmm2
.L2:
cmpq %rdx, %rax
jge .L5
vfmadd231ss (%rcx,%rax,4), %xmm2, %xmm1
incq %rax
vmulss %xmm0, %xmm2, %xmm2
jmp .L2
.L5:
vmovaps %xmm1, %xmm0
ret
.LC1: .long 0x3f800000
```
:::
```c=
struct P{
long n;
float a[];
}
float puzzle6(struct P * p , float q)
{
int n = p->n;//2
float *a = p.a;//3
float sum = 0;//5
float pow = 1.0;//6 (0x3f800000)
for(long i = 0; i<=n; i++)//4, 7-8, 10, 12
{
sum += a[i] * pow;//9
pow*= q;//11
}
return sum;
}
```
```c=
vfmadd231ss a, b, c
powieksz c przez pomnożone a przez b
c+=a*b;
vmulss a, b, c
c=a*b;
```
Wynikiem jest:
$$
\sum_{i=0}^{n} a_i*q^i
$$
# Lista 8 (30.04.2020)
### Zadanie 1
:::info
Poniżej podano zawartość pliku «swap.c»:
```cpp=
extern int buf[];
int *bufp0 = &buf[0];
static int *bufp1;
static void incr() {
static int count = 0;
count++;
}
void swap() {
int temp;
incr();
bufp1 = &buf[1];
temp = *bufp0;
*bufp0 = *bufp1;
*bufp1 = temp;
}
```
Wygeneruj plik relokowalny «swap.o», po czym na podstawie wydruku polecenia «readelf -t -s» dla każdego elementu tablicy symboli podaj:
• adres symbolu względem początku sekcji,
• typ symbolu – tj. «local», «global», «extern»,
• rozmiar danych, na które wskazuje symbol,
• numer i nazwę sekcji – tj. «.text», «.data», «.bss» – do której odnosi się symbol.
Co przechowują sekcje «.strtab» i «.shstrtab»?
:::
```
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS swap.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 8 OBJECT LOCAL DEFAULT 4 _ZL5bufp1
7: 0000000000000008 4 OBJECT LOCAL DEFAULT 4 _ZZL4incrvE5count
8: 0000000000000000 22 FUNC LOCAL DEFAULT 1 _ZL4incrv
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 9
11: 0000000000000000 0 SECTION LOCAL DEFAULT 7
12: 0000000000000000 8 OBJECT GLOBAL DEFAULT 5 bufp0
13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND buf
14: 0000000000000016 72 FUNC GLOBAL DEFAULT 1 _Z4swapv
```
```Value``` - adres
```Type``` - typ
```Size``` - rozmiar
```Ndx``` - number sekcji
| numer | nazwa |
| ----- | ------------------------- |
| UND | undefined |
| ABS | nie podlega żadnej sekcji |
| 1 | ```.text``` |
| 3 | ```.data``` |
| 4 | ```.bss``` |
| 5 | ```.data.rel``` |
| 7 | ```.comment``` |
| 8 | ```.note.GNU-stack``` |
| 9 | ```.eh_frame``` |
```.strtab``` - String Table
String Table jest trzyma stringi z nazwami symboli.
```.shstrtab``` - Section Header String Table
Section Header String Table trzyma stringi z nazwami sekcji.
### Zadanie 2
:::info
Rozważmy program składający się z dwóch plików źródłowych:
```cpp=
/* mismatch-a.c */
void p2(void);
int main(void) {
p2();
return 0;
}
```
```cpp=
/* mismatch-b.c */
#include <stdio.h>
char main;
void p2(void) {
printf("0x%x\n", main);
}
```
Po uruchomieniu program drukuje pewien ciąg znaków i kończy działanie bez zgłoszenia błędu. Czemu tak się dzieje? Skąd pochodzi wydrukowana wartość? Zauważ, że zmienna «main» w pliku «mismatch-a.c» jest niezainicjowana. Co by się stało, gdybyśmy w funkcji «p2» przypisali wartość pod zmienną «main»? Co by się zmieniło gdybyśmy w pliku «mismatch-b.c» zainicjowali zmienną «main» w miejscu jej definicji?
:::
Dzieje się tak ponieważ ```char main``` jest symbolem słabym (ponieważ jest niezainicjowany), natomiast
```int main()``` jest symbolem silnym. Konsolidator preferuje symbole silne zatem output będzie pochodził z
```int main()```. Po wykonaniu ```objdump -d mismatch-a.o``` dostajemy output
```
0000000000000000 <main>:
0: f3 0f 1e fa endbr64
4: 50 push %rax
5: e8 00 00 00 00 callq a <main+0xa>
a: 31 c0 xor %eax,%eax
c: 5a pop %rdx
d: c3 retq
```
natomiast output samego programu to ```0xfffffff3```. Można zauważyć że jest to pierwszy bajt zapisu funkcji ```int main()``` rozszerzony do 4 bajtów.
jeśli spróbujemy zainicjalizować ```char main``` to stanie się to symbol silny zatem będziemy mieli konflikt silnych symboli i spowoduje to linker error.
natomiast jeśli spróbujemy zmienić wartość ```char main``` to dostaniemy błąd podczas wykonania programu ponieważ spróbujemy zmienić zawartość kodu funkcji ```int main()``` co nie jest dozwolone.
### Zadanie 3
:::info
Zapoznaj się z narzędziami do analizy plików relokowalnych w formacie ELF i bibliotek statycznych, tj. «objdump», «readelf» i «ar»; a następnie odpowiedz na następujące pytania:
1. Ile plików zawierają biblioteki «libc.a» i «libm.a» (katalog «/usr/lib/x86_64-linux-gnu»)?
2. Czy wywołanie kompilatora z opcją «-Og» generuje inny kod wykonywalny niż «-Og -g»?
3. Z jakich bibliotek współdzielonych korzysta interpreter języka «Python» (plik «/usr/bin/python»)?
Zaprezentuj w jaki sposób można dojść do odpowiedzi korzystając z podanych poleceń.
:::
Podpunkt 1.:
1. Wejdź do katalogu «/usr/lib/x86_64-linux-gnu».
2. Wpisz ```ar t libc.a | wc -l```. Wynik: ```1690```
3. Wpisz ```cat libm.a```, aby wyświetlić ścieżki (u mnie ~/libm-2.27.a, ~/libmvec.a).
Następnie ```ar t libm-2.27.a | wc -l```; wynik: ```794```.
Potem ```ar t libmvec.a | wc -l```; wynik: ```129```.
Razem: 794 + 129 = 923.
Podpunkt 2.:
Flaga -Og wyłącza pewne optymalizacje.
Flaga -g oznacza włączenie standardowych informacji dla debuggera (można to sprawdzić szukając sekcji .debug_info).
Podpunkt 3.:
Wejdź do /usr/bin. Następnie:
```readelf -d python2.7```
```readelf -d python2.7 | grep "NEEDED"```

### Zadanie 4
:::info
Rozważmy program składający się z dwóch plików źródłowych:
```cpp=
/* str-a.c */
#include <stdio.h>
char *somestr(void);
int main(void) {
char *s = somestr();
s[5] = ’\0’;
puts(s);
return 0;
}
```
```cpp=
/* str-b.c */
char *somestr(void) {
return "Hello, world!";
}
```
Po uruchomieniu program kończy się z błędem dostępu do pamięci. Dlaczego? Gdzie została umieszczona stała znakowa "Hello, world!"? Popraw ten program tak, by się poprawnie zakończył. Gdzie został umieszczony ciąg znaków po poprawce? Nie wolno modyfikować sygnatury procedury «somestr» i pliku «str-a.c», ani korzystać z dodatkowych procedur.
:::
Po uruchomieniu program kończy się z błędem dostępu do pamięci. Dlaczego?
Dzieje się tak ponieważ próbujemy wprowadzić zmiany do read-only
W standardzie C jest napisane,że ciągi znaków które nie są wrzucone na stos lub sterte trafiają zawsze do read-only data.
Co możemy zrobić, żeby temu zapobiec?
1. Możemy wrzucić "Hello, world!" na stos.
```c=
char str[] = "Hello, world!";
```
tylko wtedy pojawi się problem, ponieważ str będzie lokalną wartością
więc po zakończeniu funkcji zostanie zdjęta ze stosu i nie będzie istnieć poza funkcją. - czyli to zmieni jeden problem na inny
2. Możemy użyć malloc żeby wrzucić "Hello, world!" na sterte
```c=
char *str = (char*)malloc(sizeof(char)*14);
```
i po kolei wrzucać literkę po literce, albo skopiować używajac strcpy.
Problem z tym rozwiązaniem jest taki, że wymaga użycia
stdlib.h
3. Możemy zadeklarować "Hello, world!" jako static
```c=
static char str[] = "Hello, world!";
```
to rozwiązanie spowoduje, że ciąg znaków "Hello, world!" zostanie umieszczony w sekcji .data dzięki czemu zwrócony wskaźnik będzie wskazywał na ciąg znaków istniejący cały czas w trakcie działania naszego programu
(tak samo jak domyślnie przy return "Hello, world!")
i będzie można zmieniać ten ciąg znaków bo nie będzie już read-only.
Jedyne co może być kłopotliwe w tym rozwiązaniu, to fakt, że przy każdym wywołaniu funkcji dostaniemy wskaźnik do tego samego ciągu znaków w .data
a więc jeśli go zmienimy gdzieś poza funkcją somestr(void) to przy kolejnym wywołaniu tej funkcji dostaniemy wskaźnik na ten sam ciąg, który został zmieniony.
```
int main(void) {
char *s = somestr();
s[5] = ’\0’;
puts(s);
s = somestr();
puts(s);
return 0;
}
```
a więc ten kod wyprodukuje w konsoli:
Hello
Hello
### Zadanie 5
:::info
Posiłkując się narzędziem «objdump» podaj rozmiary sekcji «.data» i «.bss» plików «bar.o» i «foo.o». Wskaż rozmiar i pozycje symboli względem początków odpowiednich sekcji. Wyjaśnij znaczenie opcji «-fno-common» przekazywanej do kompilatora.
```cpp=
/* bar.c */
int bar = 42;
short dead[15];
```
```cpp=
/* foo.c */
long foo = 19;
char code[17];
```
Na czym polega częściowa konsolidacja z użyciem opcji «-r» do polecenia «ld»? Czym różni się sposób wygenerowania plików «merge-1.o» i «merge-2.o»? Na podstawie mapy konsolidacji porównaj pozycje symboli i rozmiary sekcji w plikach wynikowych. Z czego wynikają różnice skoro konsolidator nie dysponuje informacjami o typach języka C?
:::
bar.o
```
[Nr] Name
Type Address Offset Link
Size EntSize Info Align
Flags
[ 2] .data
PROGBITS 0000000000000000 0000000000000040 0
0000000000000004 0000000000000000 0 4
[0000000000000003]: WRITE, ALLOC
```
O rozmiarze sekcji informuje nas parametr Size.
|Plik |Rozmiar «.data»(B)|Rozmiar «.bss»(B)|
|:---:|:----------------:|:---------------:|
|bar.o| 0x4 | 0x1e |
|foo.o| 0x8 | 0x11 |
symtab dla bar.o
```
Symbol table '.symtab' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 SECTION LOCAL DEFAULT 1
2: 0000000000000000 0 SECTION LOCAL DEFAULT 2
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 30 OBJECT GLOBAL DEFAULT 3 dead
6: 0000000000000000 4 OBJECT GLOBAL DEFAULT 2 bar
```
|Symbol| Offset| Sekcja |
|:----:|:-----:|:------:|
| dead | 0x0 | «.bss» |
| bar | 0x0 |«.data» |
symtab dla foo.o
```
Symbol table '.symtab' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 SECTION LOCAL DEFAULT 1
2: 0000000000000000 0 SECTION LOCAL DEFAULT 2
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 17 OBJECT GLOBAL DEFAULT 3 code
6: 0000000000000000 8 OBJECT GLOBAL DEFAULT 2 foo
```
|Symbol| Offset| Sekcja |
|:----:|:-----:|:------:|
| dead | 0x0 | «.bss» |
| bar | 0x0 |«.data» |
Opcja [-fno-common](https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#Code-Gen-Options) (domyślna) umieszcza niezainicjalizowane zmienne globalne w sekcji «.bss».
Opcja [-r](https://linux.die.net/man/1/ld) dla ld oznacza, że konsolidator ma wygenerować plik relokowalny.
:::info
Czym różni się sposób wygenerowania plików «merge-1.o» i «merge-2.o»?
:::
merge-1.o generujemy konsolidując częściowo foo.o z bar.o, a merge-2.o bar.o z foo.o.
| Plik |Rozmiar «.data»(B)|Rozmiar «.bss»(B)|
|:-------:|:----------------:|:---------------:|
|merge-1.o| 0xc | 0x3e |
|merge-2.o| 0x10 | 0x31 |
Na podstawie map:
merge-1.map:
```
.data 0x0000000000000000 0xc
*(.data)
.data 0x0000000000000000 0x8 foo.o
0x0000000000000000 foo
.data 0x0000000000000008 0x4 bar.o
0x0000000000000008 bar
.data1
*(.data1)
.bss 0x0000000000000000 0x3e
*(.bss)
.bss 0x0000000000000000 0x11 foo.o
0x0000000000000000 code
*fill* 0x0000000000000011 0xf
.bss 0x0000000000000020 0x1e bar.o
0x0000000000000020 dead
```
merge-2.map
```
.data 0x0000000000000000 0x10
*(.data)
.data 0x0000000000000000 0x4 bar.o
0x0000000000000000 bar
*fill* 0x0000000000000004 0x4
.data 0x0000000000000008 0x8 foo.o
0x0000000000000008 foo
.data1
*(.data1)
.bss 0x0000000000000000 0x31
*(.bss)
.bss 0x0000000000000000 0x1e bar.o
0x0000000000000000 dead
*fill* 0x000000000000001e 0x2
.bss 0x0000000000000020 0x11 foo.o
0x0000000000000020 code
```
Wynika to z tego, że sekcja może wymagać wyrównania.
Z użyciem ```readelf -t merge-1.o``` lub ```readelf -t merge-2.o``` możemy dla każej sekcji wyczytać jej wartość dla parametru Align (dostosowany jest do przechowywanych zmiennych).
W tym przypadku dla ```«.data»``` jest to 8, a dla ```«.bss»``` to 16. O dodanym paddingu informuje nas ```*fill*```.
### Zadanie 6
:::info
Plik wykonywalny powstały w wyniku kompilacji poniższych plików źródłowych powinien być nie dłuższy niż 1KiB. Na podstawie nagłówka pliku ELF wskaż w zdeasemblowanym pierwszą instrukcję, którą wykona procesor po wejściu do programu. Na podstawie nagłówków programu wskaż pod jaki adres wirtualny zostanie załadowany segment z sekcją «.text».
```cpp=
/* start.c */
int is_even(long);
void _start(void) {
asm volatile(
"syscall"
: /* no output */
: "a" (0x3c /* exit */),
"D" (is_even(42)));
}
```
```cpp=
/* even.c */
int is_odd(long n);
int is_even(long n)
if (n == 0)
return 1;
else
return is_odd(n - 1);
}
```
```cpp=
/* odd.c */
int is_even(long n);
int is_odd(long n) {
if (n == 0)
return 0;
else
return is_even(n - 1);
}
```
Wskaż w kodzie źródłowym miejsca występowania relokacji. Zidentyfikuj je w wydruku polecenia «objdump -r -d», po czym pokaż jak zostały wypełnione w pliku wykonywalnym. Na podstawie rozdziału §7.7 podręcznika „Computer Systems: A Programmer’s Perspective” zreferuj algorytm relokacji. Wydrukuj tabele relokacji plików relokowalnych, fragment mapy konsolidacji opisujący złączoną sekcję «.text», po czym oblicz ręcznie wartości, które należy wpisać w miejsce relokacji.
:::
**Plik wykonywalny** - zawiera kod i dane w formie pozwalającej na bezpośrednie skopiowanie do pamięci i uruchomienie
**Nagłowek pliku ELF** - zawiera podstawowe informacje dot. pliku takie jak: sposób zapisu danych (np. U2, little endian), typ pliku (np. relokowalny), entry point address itd.
(polecenie: readelf -h nazwa_pliku)
Przykładowo w nagłówku pliku *main* mamy entry point address = 0x40009B, czyli pierwszą wykonaną instrukcją będzie sub $0x8,%rsp
**Segment** - część pliku zawierająca kod wykonywalny.
**Nagłówek programu** - część pliku ELF, która dla danego segmentu przechowuje informacje takie jak np. offset od początku pliku, adres wirtualny, rozmiar itd.
Przykładowo dla pliku *main* mamy adres wirtualny = 0x400000 dla segmentu z sekcją .text
(polecenie: readelf -l nazwa_pliku)
**Relokacja** - scalanie kilku plików relokowalnych w jeden; następuje przy tym scalanie ze sobą odpowiednich sekcji oraz odpowiednie dopasowywanie adresów pamięci (np. przy instrukcjach call)
W naszym kodzie źródłowym obliczanie odpowiednich adresów nastąpi gdy wywołujemy:
1. w funkcji _start - funkcję is_even
2. w funkcji is_even - funkcję is_odd
3. w funkcji is_odd - funkcję is_even
W zdeasemblowanym pliku *main* (objdump -d -r main) wygląda to tak:
```=
main: file format elf64-x86-64
Disassembly of section .text:
0000000000400078 <is_even>:
400078: 48 85 ff test %rdi,%rdi
40007b: 74 08 je 400085 <is_even+0xd>
40007d: 48 ff cf dec %rdi
400080: e9 06 00 00 00 jmpq 40008b <is_odd> # relokacja
400085: b8 01 00 00 00 mov $0x1,%eax
40008a: c3 retq
000000000040008b <is_odd>:
40008b: 48 85 ff test %rdi,%rdi
40008e: 74 08 je 400098 <is_odd+0xd>
400090: 48 ff cf dec %rdi
400093: e9 e0 ff ff ff jmpq 400078 <is_even> # relokacja
400098: 31 c0 xor %eax,%eax
40009a: c3 retq
000000000040009b <_start>:
40009b: 48 83 ec 08 sub $0x8,%rsp
40009f: bf 2a 00 00 00 mov $0x2a,%edi
4000a4: e8 cf ff ff ff callq 400078 <is_even> # relokacja
4000a9: 89 c7 mov %eax,%edi
4000ab: b8 3c 00 00 00 mov $0x3c,%eax
4000b0: 0f 05 syscall
4000b2: 58 pop %rax
4000b3: c3 retq
```
W plikach *odd.o*, *even.o*, *start.o* mieliśmy odwołania do funkcji, których adresu jeszcze nie znaliśmy. W takiej sytuacji zostały utworzone relocation entries, na przykład:
```=
even.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <is_even>:
0: 48 85 ff test %rdi,%rdi
3: 74 08 je d <is_even+0xd>
5: 48 ff cf dec %rdi
8: e9 00 00 00 00 jmpq d <is_even+0xd>
9: R_X86_64_PC32 is_odd-0x4
d: b8 01 00 00 00 mov $0x1,%eax
12: c3 retq
```
W 8 bajcie funkcji is_even mamy instrukcję e9 00 00 00 00 00, przy czym te 8 zer to adres który w przyszłości (w pliku main) zostanie odpowiednio zmieniony. Relocation entry jest tu postaci 9: R_X86_64_PC32 is_odd-0x4, gdzie:
* 9 - offset od początku funkcji is_even
* R_X86_64_PC32 - typ relokacji
* is_odd-0x4 - symbol do którego relocation entry się odnosi
Gdy w pliku *main* obliczamy odpowiednie adresy, postępujemy wg algorytmu (zależy on od typu relokacji, tutaj dla każdego relocation entry mamy R_X86_64_PC32, więc poniżej znajduje się algorytm dla tego przypadku):
----------
w każdej sekcji s dla każdego relocation entry r:
1. refptr = s + r.offset (wskaźnik na adres który powinniśmy zmienić)
2. refadr = ADRES(s) + r.offset
3. *refptr = (unsigned) (ADRES(r.symbol) + *refptr - refadr)
(gdy jednak typ relocation entry to R_X86_32):
1. refptr = s + r.offset
2. *refptr = (unsigned) (ADRES(r.symbol) - *refptr)
----------
W naszym przypadku do dyspozycji mamy następujące dane:
* relocation entries (polecenie readelf -r file_name.o):
```=
is_even.o
Relocation section '.rela.text' at offset 0xf8 contains 1 entry:
Offset Info Type Sym. Value Sym. Name + Addend
000000000009 000500000002 R_X86_64_PC32 0000000000000000 is_odd - 4
------------------------------------
is_odd.o:
Relocation section '.rela.text' at offset 0xf0 contains 1 entry:
Offset Info Type Sym. Value Sym. Name + Addend
000000000009 000500000002 R_X86_64_PC32 0000000000000000 is_even - 4
------------------------------------
start.o:
Relocation section '.rela.text' at offset 0x100 contains 1 entry:
Offset Info Type Sym. Value Sym. Name + Addend
00000000000a 000500000002 R_X86_64_PC32 0000000000000000 is_even - 4
```
* fragment z pliku main.map (mamy tutaj adresy poszczególnych sekcji):
```=
.text 0x0000000000400078 0x3c
*(.text.unlikely .text.*_unlikely .text.unlikely.*)
*(.text.exit .text.exit.*)
*(.text.startup .text.startup.*)
*(.text.hot .text.hot.*)
*(.text .stub .text.* .gnu.linkonce.t.*)
.text 0x0000000000400078 0x13 even.o
0x0000000000400078 is_even
.text 0x000000000040008b 0x10 odd.o
0x000000000040008b is_odd
.text 0x000000000040009b 0x19 start.o
0x000000000040009b _start
```
Mamy więc do obliczenia 3 adresy, jakie powiny zostać zmienione po relokacji:
1. w funkcji is_even, wywołanie is_odd:
r.offset = 0x9
r.symbol = is_odd - 0x4
ADRES(s) = 0x400078
ADRES(r.symbol) = 0x40008B
i teraz zgodnie z algorytmem:
refptr = 0x400078 + 0x9
refadr = 0x400078 + 0x9
*refptr = 0x40008B - 0x4 + 0 - (0x400078 + 0x9) = **0x6**
2. w funkcji is_odd, wywołanie is_even:
r.offset = 0x9
r.symbol = is_even - 0x4
ADRES(s) = 0x40008B
ADRES(r.symbol) = 0x400078
refptr = 0x40008B + 0x9
refadr = 0x40008B + 0x9
*refptr = 0x400078 - 0x4 + 0 - (0x40008B + 0x9) = **0xFFFFFFE0**
3. w funkcji _start, wywołanie is_even:
r.offset = 0xA
r.symbol = is_even - 0x4
ADRES(s) = 0x40009B
ADRES(r.symbol) = 0x400078
refptr = 0x40009B + 0xA
refadr = 0x40008B + 0xA
*refptr = 0x400078 - 0x4 + 0 - (0x40009B + 0xA) = **0xFFFFFFCF**
Możemy sprawdzić obliczenia patrząc na zdeasemblowany plik *main* (pamiętając, że tutaj adresy są zapisane w Little Endian):
```=
main: file format elf64-x86-64
Disassembly of section .text:
0000000000400078 <is_even>:
400078: 48 85 ff test %rdi,%rdi
40007b: 74 08 je 400085 <is_even+0xd>
40007d: 48 ff cf dec %rdi
400080: e9 06 00 00 00 jmpq 40008b <is_odd>
400085: b8 01 00 00 00 mov $0x1,%eax
40008a: c3 retq
000000000040008b <is_odd>:
40008b: 48 85 ff test %rdi,%rdi
40008e: 74 08 je 400098 <is_odd+0xd>
400090: 48 ff cf dec %rdi
400093: e9 e0 ff ff ff jmpq 400078 <is_even>
400098: 31 c0 xor %eax,%eax
40009a: c3 retq
000000000040009b <_start>:
40009b: 48 83 ec 08 sub $0x8,%rsp
40009f: bf 2a 00 00 00 mov $0x2a,%edi
4000a4: e8 cf ff ff ff callq 400078 <is_even>
4000a9: 89 c7 mov %eax,%edi
4000ab: b8 3c 00 00 00 mov $0x3c,%eax
4000b0: 0f 05 syscall
4000b2: 58 pop %rax
4000b3: c3 retq
```
### Zadanie 7
:::info
W trakcie tłumaczenia poniższego kodu na asembler kompilator umieścił tablicę skoków dla instrukcji przełączania w sekcji «.rodata».
```cpp=
int relo3(int val) {
switch (val) {
case 100:
return val;
case 101:
return val + 1;
case 103:
case 104:
return val + 3;
case 105:
return val + 5;
default:
return val + 6;
}
}
```
```
0000000000000000 <relo3>:
0: 8d 47 9c lea -0x64(%rdi),%eax
3: 83 f8 05 cmp $0x5,%eax
6: 77 15 ja 1d <relo3+0x1d>
8: 89 c0 mov %eax,%eax
a: ff 24 c5 00 00 00 00 jmpq *0x0(,%rax,8)
11: 8d 47 01 lea 0x1(%rdi),%eax
14: c3 retq
15: 8d 47 03 lea 0x3(%rdi),%eax
18: c3 retq
19: 8d 47 05 lea 0x5(%rdi),%eax
1c: c3 retq
1d: 8d 47 06 lea 0x6(%rdi),%eax
20: c3 retq
21: 89 f8 mov %edi,%eax
23: c3 retq
```
Dla sekcji «.text» i «.rodata» określ pod jakimi miejscami znajdują się relokacje, a następnie podaj zawartość tablicy relokacji «.rela.text» i «.rela.rodata», tj. listę rekordów składających się z:
• przesunięcia relokacji względem początku sekcji,
• typu relokacji,
• nazwy symbolu i przesunięcia względem początku symbolu.
W wyniku konsolidacji pliku wykonywalnego zawierającego procedurę «relo3», została ona umieszczona pod adresem 0x1000, a tablica skoków pod 0x2000. Oblicz wartości, które należy wstawić w miejsca relokacji.
:::
### Zadanie 8
:::info
Na podstawie rozdziału §7.12 podręcznika „Computer Systems: A Programmer’s Perspective” opisz proces leniwego wiązania (ang. lazy binding) symboli i odpowiedz na następujące pytania:
• Czym charakteryzuje się kod relokowalny (ang. Position Independent Code)?
• Do czego służą sekcje «.plt» i «.got» – jakie dane są tam przechowywane?
• Czemu sekcja «.got» jest modyfikowalna, a sekcje kodu i «.plt» są tylko do odczytu?
• Co znajduje się w sekcji «.dynamic»?
Zaprezentuj leniwe wiązanie na podstawie programu «lazy». Uruchom go pod kontrolą debuggera GDB, ustaw punkty wstrzymań (ang. breakpoint) na linię 4 i 5. Po czym wykonując krokowo program (stepi) pokaż, że gdy procesor skacze do adresu procedury «puts» zapisanego w «.got» – za pierwszym wywołaniem jest tam przechowywany inny adres niż za drugim.
Wskazówka: Wykorzystaj rysunek 7.19. Dobrze jest zainstalować sobie GDB dashboard.
:::
###### tags: `github` `ask`