PROGRAMOWANIE NISKOPOZIOMOWE PN.06 c Dr inż. Ignacy Pardyka UNIWERSYTET JANA KOCHANOWSKIEGO w Kielcach Rok akad. 2011/2012 1 2 Ćwiczenia laboratoryjne c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 1 / 22 c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 2 / 22 Przykład struktury służą do grupowania danych w ramach jednej zmiennej złożonej zwiększanie przejrzystości kodu łatwiejsze przekazywanie danych do funkcji poprawa lokalności kodu implementacja w asemblerze jako tablice o zmiennych rozmiarach elementy o różnych rozmiarach identyfikacja elementu przez nazwę (lub znacznik) adres startowy struktury offset elementu (wyznacza kompilator) do funkcji przekazywać można przez wartość (kopia na stos!) przez referencję (wskaźnik do struktury) przykładowa struktura w języku C struct S { short int x; // 2 bajty int y; // 4 bajty double z; // 8 bajtów funkcja z parametrem wskazującym na strukturę S ; void zero_y(s * sp); %define y_offset 4 _zero_y: enter 0,0 mov eax,[ebp+8]; EAX = sp mov dword [eax+y_offset],0 ; sp->y = 0 leave c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 3 / 22 c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 4 / 22
Zmienne w strukturze Pola bitowe struktury makro (w stddef.h) zwracające offset elementu, np. offsetof(s,y) kompilator gcc układa zmienne w strukturze domyślnie na granicy podwójnego słowa można zdefiniować typ zmiennych umieszczanych na granicy bajtu (1), słowa (2) albo podwójnego słowa (4) jako parametr dla alligned typedef short int unaligned_int attribute ((alligned(1))); strukturę można pakować na minimum bajtów: struct S {... } attribute ((packed)); w strukturze C można definiować pola bitowe, np. struct S { unsigned f1: 3; // 3 LSB unsigned f2:10; unsigned f3:11; unsigned f4: 8; // 8 MSB zmienna typu S (32-bitowa) będzie udostępniać pola bitowe, na których można wykonywać operacje uwaga na układ bajtów zmiennej wielobajtowej w pamięci: big endian, little endian częste zastosowanie: dane dla kontrolerów urządzeń c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 5 / 22 c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 6 / 22 Przeciążanie funkcji w C++ Jak kompilator C++ modyfikuje nazwy funkcje w C++ mogą być przeciążone (overloaded): ta sama nazwa do identyfikacji różnych funkcji #include <stdio.h> void f(int x){ printf("%d\n",x);} void f(double x){ printf("%g\n",x);} DJGPP modyfikuje nazwy dodając przyrostek F z kodem typu argumentu: dla integer: _f Fi dla double : _f Fd modyfikacja nazw (ang. mangling) gdy więcej argumentów funkcji, np. void f(int i, int y, double z) typy argumentów kodowane kolejno _f Fiid identycznie modyfikowane są nazwy zmiennych globalnych a także funkcje standardowe, np. prototyp int printf(const char *,...); po kompilacji nazwa funkcji: _printf FPCce gdzie kolejno: F funkcja, P pointer, C const, c character, e ellipsis gdy trzeba wołać funkcje z biblioteki C, to wskazać kompilatorowi: extern "C" int printf(const char *,...); c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 7 / 22 c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 8 / 22
referencje służą do przekazywania parametrów do funkcji bez konieczności używania wskaźników explicite, np. void f(int &x){ x++;} int main(){ int y = 5; f(y); // kompilator użyje adresu y printf("%d\n", y); urn 0; } odpowiednią funkcję f piszemy w asemblerze tak, jakby prototyp był: void f(int * xp); c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 9 / 22 inline eliminuje konieczność wywoływania funkcji realizującej prosty kod, np. inline int g(int x){ urn x*x;} int main(){ int y, x = 5; y = g(x); printf("%d\n", y); urn 0; } y = g(x) zostanie skompilowane bez korzystania z instrukcji call mov eax,[ebp-8] ; imul eax,eax mov [ebp-4],eax ; x y c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 10 / 22 Funkcje składowe klasy Simple opisują typ obiektu klasa jest specjalną strukturą zawierającą składniki dane metody (funkcje składowe) przechowywane w pamięci poza strukturą class Simple{ Simple(); // konstruktor obiektów ~Simple(); // destruktor int get_data() const; void set_data(int); private: int data; } kompilator generuje kod w asemblerze służący do przekazania funkcji składowej niejawnego parametru wskaźnika this do obiektu danej klasy (tj. tego, na którym metoda ma operować) Simple::Simple(){ data = 0;} Simple::~Simple(){} int Simple::get_data() const { urn data; } void Simple::set_data(int x) { data = x; } c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 11 / 22 c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 12 / 22
Kod asemblera dla funkcji składowej B od A funkcja składowa set_data(int x) w konwencji NASM _set_data 6Simplei: push ebp mov ebp, esp mov eax, [ebp+8]; EAX = this mov edx, [ebp+12]; EDX = x mov [eax], edx ; offset dla data jest zero leave offset = 0, bo tylko data w strukturze klasy Simple jako ostatni parametr wywołania tej funkcji na stos odkładany musi być adres obiektu, tj. this tak jakby prototyp w C był: void set_data(simple * this, int x); #include <cstddef> #include <iostream> using namespace std; class A { void cdecl m() { // Microsoft this przekazałby przez ECX cout << "A::m()" << endl;} int ad; class B : public A { void cdecl m() { cout << "B::m()" << endl;} int bd; c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 13 / 22 c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 14 / 22 Funkcja otrzymująca wskaźnik do obiektu funkcja f void f(a *p) { p->ad = 5; p->m();} w asemblerze zawiera sztywne wywołanie metody A::m() _f FP1A: push ebp mov ebp, esp mov eax, [ebp+8]; adres obiektu mov dword [eax], 5; mov eax, [ebp+8]; push eax ; adres obiektu na stos call _m 1A ; "sztywne" wywołanie A::m() add esp, 4 leave aby funkcja f wywołała metodę B::m() gdy parametrem f jest wskaźnik do obiektu klasy B, trzeba wskazać kompilatorowi C++ by tworzył kod asemblera dla metod wirtualnych, np. #include <cstddef> #include <iostream> using namespace std; class A { virtual void cdecl m() { cout << "A::m()" << endl;} int ad; class B : public A { virtual void cdecl m() { cout << "B::m()" << endl;} int bd; c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 15 / 22 c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 16 / 22
Kod asemblera dla funkcji f wywołującej metody wirtualne kompilator dla każdej klasy tworzy tablicę vtable, w której umieszcza adresy metod wirtualnych jeśli metoda virtualna jest dostępna w B na zasadzie dziedziczenia z A, to w tablicy wirtualnej dla obiektów klasy B umieszczany jest adres metody zdefiniowanej w A kompilator Windows, np. g++ umieszcza adres tablicy vtable w dodatkowym polu na początku struktury obiektu danej klasy (offset = 0) właściwa metoda wywoływana za pomocą instrukcji call z adresem metody wziętym z tablicy vtable jest to przykład późnego wiązania (ang. late binding)?f@@yaxpava@@@z: push ebp mov ebp, esp mov eax, [ebp+8] mov dword [eax+4], 5 ; p->ad = 5; mov ecx, [ebp+8] ; ecx = p mov edx, [ecx] ; adres vtable pod offset = 0 obiektu push eax ; push "this" pointer call dword [edx] ; jest tylko jedna metoda wirtualna add esp, 4 pop ebp c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 17 / 22 c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 18 / 22 Budowanie programu z modułów C++ i asemblera > nasm -f coff big_math.asm > g++ -c big_int.cpp > g++ -c test_big_int.cpp > g++ -o test_big_int test_big_int.o big_int.o big_math.o Ćwiczenia laboratoryjne 1 2 Ćwiczenia laboratoryjne c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 19 / 22 c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 20 / 22
Ćwiczenia laboratoryjne Ćwiczenia laboratoryjne 1 Napisać obiektowy program wielomodułowy (moduł z funkcją main() w C++, poniższe funkcje składowe klas w modułach asemblera), taki że: funkcje napisane w asemblerze wykonują wskazane operacje bitowe na obiektach zawierających pole danych w postaci dwuwymiarowej tablicy danych typu całkowitego. Deklaracje klas i wejście/wyjście realizuje moduł w języku C++. operacja AND(A, B) operacja OR(A, B) operacja XOR(A, B) operacja NOT (A) operacja NAND(A, B) 2 Napisać program (funkcje w asemblerze), który będzie kodował wprowadzany ze standardowego wejścia łańcuch znaków ASCII w tablicy 64 64 bajtów (np. pikseli obrazu) wykorzystując do tego celu 2 najmłodsze bity każdego elementu tablicy (zastosować strukturę z polami bitowymi). c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 21 / 22 A. S. Tanenbaum, Strukturalna organizacja systemów komputerowych, Helion, 2006. J. Biernat, Architektura komputerów, OWPW, 2005. R. Hyde, Profesjonalne programowanie, Helion, 2005. R. Hyde, Asembler. Sztuka programowania, Helion, 2004. G. Mazur, Programowanie niskopoziomowe, http://wazniak.mimuw.edu.pl. P.A. Carter, PC Assembly Language, http://www.drpaulcarter.com/pcasm/. D.W. Lewis, Między asemblerem a językiem C. Podstawy oprogramowania wbudowanego, RM, 2004. c Dr inż. Ignacy Pardyka (Inf.UJK) PN.06 Rok akad. 2011/2012 22 / 22