User Tools

Site Tools


No ACL setup yet! Denying access to everyone.
pl:edu:zmitac:n1z:ja:lab1

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
pl:edu:zmitac:n1z:ja:lab1 [2021/03/12 17:07] – [Tworzenie projektu C++ i konsersja do ASM] pczekalskipl:edu:zmitac:n1z:ja:lab1 [2022/04/23 19:04] (current) – [Powtórka z teorii: Funkcje i ich wywoływanie z poziomu assemblera] pczekalski
Line 4: Line 4:
 Laboratorium języków assemblerowych, ćwiczenie 1 Laboratorium języków assemblerowych, ćwiczenie 1
  
-==== Środowisko sprzętowe i programistyczne ====+==== Przygotowanie: Środowisko sprzętowe i programistyczne ====
 Do wykonania ćwiczenia niezbędny jest komputer PC w postaci maszyny fizycznej lub wirtualnej z systemem Windows 7, 8, 8.1 lub 10, z zainstalowanym i skonfigurowanym środowiskiem programistycznym Microsoft Visual Studio 2019 w wrsji co najmniej Community Edition ((https://visualstudio.microsoft.com/vs/)). Do pracy w laboratorium nada się również wersja VS Professional lub Enterprise. Proszę natomiast nie pomylić z Visual Studio Code, to zupełnie inny produkt, który nie nadaje się wprost do wykorzystania w naszym laboratorium do realizacji zadań, w szczególności kolejnych ćwiczeń. Do wykonania ćwiczenia niezbędny jest komputer PC w postaci maszyny fizycznej lub wirtualnej z systemem Windows 7, 8, 8.1 lub 10, z zainstalowanym i skonfigurowanym środowiskiem programistycznym Microsoft Visual Studio 2019 w wrsji co najmniej Community Edition ((https://visualstudio.microsoft.com/vs/)). Do pracy w laboratorium nada się również wersja VS Professional lub Enterprise. Proszę natomiast nie pomylić z Visual Studio Code, to zupełnie inny produkt, który nie nadaje się wprost do wykorzystania w naszym laboratorium do realizacji zadań, w szczególności kolejnych ćwiczeń.
  
Line 37: Line 37:
 <caption>Opcje instalacyjne część 3 - uwaga - frameworki .NET po prawej stronie trzeba zaznaczyć ręcznie!</caption> <caption>Opcje instalacyjne część 3 - uwaga - frameworki .NET po prawej stronie trzeba zaznaczyć ręcznie!</caption>
 </figure> </figure>
 +
 +=== Dodatek AsmDude do VS 2019 ===
 +Visual Studio natywnie nie posiada narzędzi wspomagających pracę dewelopera z kodem źródłowym w assemblerze. Jednak ze względu na istniejących mechanizm dodatków, można doinstalować zewnętrzne narzędzia. Nie jest to obowiązkowe, ale upraszcza pracę dewelopera, w szczególności powoduje, że kod w assemblerze zostanie pokolorowany i łatwiej będzie znaleźć ew. błędy, podczas debuggowania będą podpowiadane automatycznie wartości rejestrów, jak równnież będzie się pojawaiała (w ograniczonym zakresie) kontekstowa informacja o znaczeniu instrukcji, dyrektyw, zmiennych, etykiet, etc. Dodatek AsmDude można zainstalować za pomocą mechanizmów VS w automatyczny sposób.
 +Opis dodatku znajduje się na stronie autora((https://marketplace.visualstudio.com/items?itemName=Henk-JanLebbink.AsmDude)).
  
 ==== Wymagania ==== ==== Wymagania ====
Line 49: Line 53:
  
 Takie podejście wymaga ręcznego przekonfigurowania kilku pramatetrów we właściwościach projekt, a w szczególności: Takie podejście wymaga ręcznego przekonfigurowania kilku pramatetrów we właściwościach projekt, a w szczególności:
-  * zmiany kompilatora na''masm'' +  * zmiany kompilatora na ''masm''; 
-  * zmiany docelowej platformy na x64 +  * zmiany docelowej platformy na x64; 
-  * poadnia nazwy głównej funkcji jako punktu wejścia ([Entry Point]) tak, aby sytem operacyjny wiedział, którą funkcję ma zawołać po uruchomieniu aplikacji - w wielu językach i środowiskach programowania jest to domyślnie funkcja ''main()'', natomaist w przypadku assemblera, nie ma takiej reguły i trzeba podać samemu, o którą funkcję chodzi; +  * podania nazwy głównej funkcji jako punktu wejścia ([Entry Point]) tak, aby sytem operacyjny wiedział, którą funkcję ma zawołać po uruchomieniu aplikacji - w wielu językach i środowiskach programowania jest to domyślnie funkcja ''main()'', natomaist w przypadku assemblera, nie ma takiej reguły i trzeba podać samemu, o którą funkcję chodzi; 
-  * uzupełnienia brakujących bibliotek: od wersji 2019 środowiska (w szczególności pod Windows 10) cześć standardowych funkcji dostępnych uprzednio domyślnie w bibliotekach linkowanych do każdego projektu, aktualnie zostało wyłączonych i trzeba je jawnie wskazać; uwaga - dokładny skład bibliotek jest zależny oczywiście od tego, co jest wykorzystywane (wywoływane) w kodzie źródłowym - w środowisku produkcyjnym należy zoptymalizować kod poprzez usunięcie zbędnych bibliotek+  * uzupełnienia brakujących bibliotek: od wersji 2019 środowiska (w szczególności pod Windows 10) cześć standardowych funkcji dostępnych uprzednio domyślnie w bibliotekach linkowanych do każdego projektu, aktualnie zostało wyłączonych i trzeba je jawnie wskazać; uwaga - dokładny skład bibliotek jest zależny oczywiście od tego, co jest wykorzystywane (wywoływane) w kodzie źródłowym - w środowisku produkcyjnym należy zoptymalizować kod poprzez usunięcie zbędnych bibliotek;
  
  
-<note warning>Uwaga! Ponieważ wykorzystujemy pliki ''asm,'' które natywnie nie są rozpoznawane przez Visual Studio jako pliki z kodem źródłowym, nie są one automatycznie zapisywane w momencie wywołania kompilacji, jak ma to miejsce w przypadku plików ''.cpp, .cs'' itp. Co prawda istnieje możliwość nauczenia VS 2019, aby plik był uznawany za źródłowy, niemniej  jednak wykracza to poza niniejszą instrukcję. Proszę zatem pamiętać za każdym razem kliknąć zapis do pliku (Ctrl+s) przed kompilacją.</note>+<note important>Uwaga! Ponieważ wykorzystujemy pliki ''asm,'' które natywnie nie są rozpoznawane przez Visual Studio jako pliki z kodem źródłowym, nie są one automatycznie zapisywane w momencie wywołania kompilacji, jak ma to miejsce w przypadku plików ''.cpp, .cs'' itp. Co prawda istnieje możliwość nauczenia VS 2019, aby plik był uznawany za źródłowy, niemniej  jednak wykracza to poza niniejszą instrukcję. Proszę zatem pamiętać za każdym razem kliknąć zapis do pliku (Ctrl+s) przed kompilacją.</note>
  
 ==== Powtórka z teorii: Funkcje i ich wywoływanie z poziomu assemblera ==== ==== Powtórka z teorii: Funkcje i ich wywoływanie z poziomu assemblera ====
Line 106: Line 110:
  
 == Zwrot wartości z funkcji == == Zwrot wartości z funkcji ==
-Wartość całkowita (w tym wskaźnik) zwracana jest w rejestrze RCX.\\ +Wartość całkowita (w tym wskaźnik) zwracana jest w rejestrze RAX.\\ 
 Wartości zmiennoprzecinkowa zwracana jest w rejestrze XMM0. Wartości zmiennoprzecinkowa zwracana jest w rejestrze XMM0.
  
 +== Rejestry "bezpiecznie" i "niebezpieczne" ==
 +<note warning>Niektóre z rejestrów musza pozostać niezmienione w momencie powrotu z funkcji, w stosunku do ich zawartości na początku, zaraz po wywołaniu funkcji (tzw. non-volatile). Inne rejestry można dowolnie zmieniać i nie będzie miało to wpływu na działanie systemu operacyjnego (volatile).\\ W przypadku konieczności skorzystania z tej pierwszej grupy, zawartość rejestru można odłożyć na stos lub zachować w pamięci i przywrócić przed wykonaniem instrukcji ''ret''.</note>
 +
 +Rejestry, które można bezkarnie zmieniać wewnątrz kodu assemblerowej funkcji, bez konieczności przywracania oryginalnej wartości, to ([[https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170#callercallee-saved-registers|x64 calling conventions]]):
 +  * RAX, RCX, RDX, R8, R9, R10, R11, oraz XMM0-XMM5,
 +  * dodatkowo w procesorach, które są wyposażone w rejestry YMM, górne połowy rejestrów YMM0-YMM15 również można zmieniać bez konieczności przywracania ich wartości,
 +  * a ponadto w procesorach wyposażonych w funkcje AVX512VL, wszystkie rejestry ZMM, YMM oraz XMM16-XMM31.
 +Rejestry, których nie wolno zmieniać (albo raczej należy przywrócić ich oryginalne wartości przed opuszczeniem funkcji) to:
 +  * RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15 oraz XMM6-XMM15.
 ==== Praktyka: Tworzenie projektu C++ i konsersja do ASM ==== ==== Praktyka: Tworzenie projektu C++ i konsersja do ASM ====
  
Line 147: Line 160:
 </figure> </figure>
  
-=== Utworzenie punktu startowego ===+=== Utworzenie punktu startowego oraz uzupełnienie bibliotek ===
 W tym kroku poinformujemy linker, jaki punkt startowy ma wpisać do pliku wykonywanego. W tym kroku poinformujemy linker, jaki punkt startowy ma wpisać do pliku wykonywanego.
 W językach wysokiego poziomu jest to domyślnie funkcja ''main()''. W przypadku assemblera nie ma takiego założenia, a zatem kod źródłowy musi być zgodny z ustawioną tutaj wartością. W językach wysokiego poziomu jest to domyślnie funkcja ''main()''. W przypadku assemblera nie ma takiego założenia, a zatem kod źródłowy musi być zgodny z ustawioną tutaj wartością.
Line 171: Line 184:
 </figure> </figure>
  
 +=== Oznaczenie pliku ASM do kompilacji ===
 +W niektórych przypadkach zdarza się, że plik ''.asm'' nie jest automatycznie rozpoznawany jako plik źródłowy z kodem w assemblerze. W związku z tym należy jawnie poinformować o tym środowiko. W tym celu należy kliknąć nazwę pliku w drzewie projektu prawym przyciskiem i wybrać opcję ''Properites'', a następnie w sekcji ''General'' wskazać ''Item Type'' i ustawić jego wartośc na ''Microsoft Macro Assembler''. Szczegóły na rysunku {{ref>jalabstep6}}.
 +<figure jalabstep6>
 +{{:pl:edu:zmitac:n1z:ja:virtualbox_msedge_-_win10_27_03_2021_19_44_41.png?600|}}
 +<caption>Oznaczenie pliku ASM jako pliku z kodem źródłowym w assemblerze</caption>
 +</figure>
  
-==== Zadanie do wykonania ====+=== Instalacja dodatku kolorującego kod źródłowy w assemblerze === 
 +Na sieci jest dostępne rozszerzenie do Visual Studio o nazwie **AsmDude**, które (m.in.) automatycznie koloruje kod źródłowy w assemblerze, pomaga w pisaniu kodu (podaje nazwy etykiet, zmiennych, itp) oraz podczas debbugowania wyświetla okienka z wartościami rejestrów. Plugin ten może zostać zainstalowany poprzez managera rozszerzeń Visual Studio((https://marketplace.visualstudio.com/items?itemName=Henk-JanLebbink.AsmDude)). 
 +==== Praktyka: Zadanie do wykonania ====
 Zaimplementuj odczyt daty (użyj funkcji ''GetSystemTime''(sysinfoapi.h)((https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtime)) odczytującej strukturę ''_SYSTEMTIME''(minwinbase.h)((https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime)). Struktura składa się z 8 wartości szesnastobitowych. Zaimplementuj odczyt daty (użyj funkcji ''GetSystemTime''(sysinfoapi.h)((https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtime)) odczytującej strukturę ''_SYSTEMTIME''(minwinbase.h)((https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime)). Struktura składa się z 8 wartości szesnastobitowych.
  
Line 185: Line 206:
 Możesz dodatkowo wyświetlić bieżącą godzinę, minuty i sekundy. Możesz dodatkowo wyświetlić bieżącą godzinę, minuty i sekundy.
  
 +=== Fragmenty kodu do wykorzystania ===
 +Pamiętaj o deklaracji segmentu kodu i danych za pomocą operatorów ''.data'' i ''.code''. Na końcu kodu obowiązkowo umieść klauzulę ''END''. 
  
 +== Segment danych ==
 +Każdy łańcuch tekstowy musi kończyć się znacznikem o binarnej wartości 0. Nie używamy polskich znaków, ponieważ korzystamy z zestawu znaków ASCII (8-bit), a nie Unicode.\\
 +Przykładowy segment danych, który był używany w prezentacji wygląda tak:\\
 +<code>
 +...
 +
 +.data
 +STD_INPUT_HANDLE = -10
 +STD_OUTPUT_HANDLE = -11
 +STD_ERROR_HANDLE = -12
 +
 +handler dq 0
 +stdHandle dq 0
 +datetimeStruct dw 16 dup (0)
 +hello_msg db "Hello world", 0
 +info_msg  db "Info", 0
 +title_msg db "Window Title JA",0
 +text_msg db "A to jest bardzo dlugi tekst do wyswietlenia w konsoli, ale bez polskich znakow!",13,10
 +dummy dq 0
 +
 +...
 +
 +</code>
 +
 +Aby móc skorzystać z zewnętrznych funkcji Windows API, trzeba poinformować assembler, jak one się nazywają, aby linker mógł potem dopasować nazwy z Twojego kodu do tych, które znajdują się w bibliotece.\\
 +W tym celu trzeba użyć dyrektywy ''EXTERN'' oraz ''PROC'':\\
 +<code>
 +
 +...
 +
 +includelib          kernel32.lib  
 +
 +EXTERN MessageBoxA: PROC
 +EXTERN GetForegroundWindow: PROC
 +EXTERN WriteConsoleA: PROC
 +EXTERN SetConsoleTitleA: PROC
 +EXTERN GetStdHandle: PROC
 +EXTERN GetSystemTime: PROC
 +
 +...
 +
 +</code>
 +Zwyczajowo umieszczamy również informację o bibliotece.
 +
 +== Segment kodu ==
 +Po uruchomieniu programu musimy zachować wartości pewnych rejestrów, w celu ich późniejszego przywrócenia. Na sam koniec należy również wywołać funkcję kończącą program i zwracającą tzw kod powrotu do systemu operacyjnego.\\
 +
 +Minimalny zestaw instrukcji wygląda następująco: 
 +<code>
 +...
 +.code 
 +hello_world_asm PROC
 +push rbp ; save frame pointer
 +mov rbp, rsp ; fix stack pointer
 +sub rsp, 8 * (4 + 2)
 +
 +.... ; tutaj kod Twojego programu
 +
 +
 +mov rsp, rbp
 +pop rbp
 +ret
 +hello_world_asm ENDP
 +END
 +</code>
 +<note tip>Uwaga, w powyższym przykładzie, główną procedurą jest ''hello_world_asm'' i to ona została wskazana jako [Entry Point] (punkt wejścia) do programu, w konfiguracji opisanej powyżej. Dostosuj do Twojego nazewnictwa.
 +</note>
 +
 +Jak widać, całość sprowadza się do zarezerwowania i odłożenia na stosie przestrzeni na tzw. shadow registry, czyli na zachowanie wartości rejestrów i przywrócenie ich po zakończeniu wykonywania programu.
 +
 +W programie wykorzystujemy interakcję z elementami graficznymi takimi jak okienko główne (Foreground Window) oraz strumień danych (STD_OUT). Podczas wywoływania funkcji, należy jednoznacznie wskazać, względem którego obiektu graficznego chcemy wykonać interakcję lub stworzyć obiekt potomny (np. MessageBox).\\
 +W tym celu musimy pobrać dwa uchwyty (handlery):
 +  * uchwyt do bieżącego głównego okienka aplikacji na poziomie systemu operacyjnego (tzw. Foreground Window)
 +  * uchwyt do strumienia danych typu terminalowego, do którego można wpisywać informacje.
 +<note important>Uwaga! Aby wyświetlić coś na konsoli, należy zawsze nacisnąć Enter, nawet ... wirtualnie. A zatem dowolny ciąg znaków przekierowany na STD_OUT pojawi się na konsoli o ile ... na końcu znajdzie się specjalny kod oznaczający znak ENTER. Są to dwie wartości numeryczne, w kolejności 13 i 10. Stąd właśnie w przykładie w segmencie danych, po treści znajduje się '',13,10''. Jeśli tego nie zrobisz, konieczne będzie przejście do okienka i ręczne naciśnięcie ENTER w celu wyświetlenia przekierowanego tam ciągu znaków z Twojego programu. Inaczej okienko pozostanie puste i tekst nie pojawi się!</note>
 +
 +W celu odczytania uchwytów przydatne będą dwie funkcje z Windows API, które zwrócą nam wskaźniki (uchwyty) do odpowiednich obiektów. Wartości te należy zachować i można je wykorzystywać wielokrotnie.
 +
 +Odczyt uchwytu do okienka głównego, ten uchwyt należy zastosować przy wywoływaniu okna dialogowego typu Message Box (i każdego innego):
 +<code>
 +...
 +call GetForegroundWindow
 +mov handler, rax
 +...
 +</code>
 +W powyższym przykładzie zwracana przez funkcję wartość (przez rejestr RAX) zapisywana jest w zmiennej ''handler'', typu DQ (64 bity, czyli po prostu adres w pamięci).
 +
 +Aby odczytać uchwyt do wnętrza terminala, należy skorzystać z innej funkcji:
 +<code>
 +...
 +mov rcx, STD_OUTPUT_HANDLE
 +call GetStdHandle
 +mov stdHandle, rax
 +...
 +</code>
 +Przy czym ta funkcja wymaga jednego argumentu, o który strumień danych nam chodzi. Są trzy możliwości:
 +  * STD_INPUT_HANDLE = -10 - standardowe wejście - można go użyć do odczytu danych z konsoli
 +  * STD_OUTPUT_HANDLE = -11 - standardowe wyjście - jego używamy aby coś napisać w terminalu; pamiętaj o kończącym ENTER, czyli ''13,10''!;
 +  * STD_ERROR_HANDLE = -12 - strumień dla błędów;
 +
 +Przykładowy kod wyswietlający na konsoli tekst znajdujący się w zmiennej (pod adresem) ''text_msg'' znajduje się poniżej. Pierwszy parametr to odczytany wyżej uchwyt do konsoli. W trzecim parametrze należy podać, ile znaków ma zostać wyświetlonych. Czwarty parametr to wskaźnik do miejsca, gdzie funkcja zwróci informacje, ile tak na prawdę wyświetliła znaków (nie do końca wiadomo, czemu to służy).
 +<code>
 +...
 +; WINAPI WriteConsoleA(
 +;  RCX => _In_ HANDLE  hConsoleOutput,
 +;  RDX => _In_ const VOID    *lpBuffer,
 +;  R8 => _In_ DWORD   nNumberOfCharsToWrite,
 +;  R9 => _Out_opt_ LPDWORD lpNumberOfCharsWritten,
 +mov rcx, stdHandle
 +lea rdx, text_msg
 +mov r8, sizeof text_msg
 +mov r9, dummy
 +call WriteConsoleA
 +...
 +</code>
 +
 +W celu wyświetlenia okienka dialogowego, należy użyć wskaźnika do okna głównego aplikacji. Jako parametry przekazywane są w kolejności: uchwyt, dwa wskaźniki, do tekstów (muszą kończyć się zerem, RDX to wskaźnik do treści, którą wyświetli dialog, a R8 wskaźnik do nagłówka okienka, który zotanie nadany okienku), które mają pojawić się w nagłówku oraz treści dialogu oraz jako ostatni, maska binarna określająca, jakie mają być zastosowane przyciski i ikony w okienku dialogowym((https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxa)).
 +<code>
 +...
 +; WINUSERAPI int WINAPI MessageBoxA(
 +;  RCX =>  _In_opt_ HWND hWnd,
 +;  RDX =>  _In_opt_ LPCSTR lpText,
 +;  R8  =>  _In_opt_ LPCSTR lpCaption,
 +;  R9  =>  _In_ UINT uType);
 +mov rcx, handler
 +mov rdx, offset hello_msg
 +mov r8, offset info_msg
 +mov r9, 0 ; MB_OK
 +and rsp, not 8 
 +call MessageBoxA
 +...
 +</code>
  
 +<note important>Uwaga - w powyższym przypadku wykonano jawnie wyrównanie stosu do wartości podzielnej przez 16 (''and rsp, not 8''). W większości przypadków nie jest to konieczne, ponieważ organizacja danych najczęściej jest 64-bitowa. Gdyby jednak okazało się, że gdzieś został odłożony na stosie pojedynczy bajt lub słowo, ulegnie to zmianie i program może rzucić wyjątkiem. W związku z tym w takim przypadku należy zadbać o wyrównanie wskaźnika stosu tak, aby był podzielny przez 16.</note>
 ==== Debuggowanie i uruchamianie ==== ==== Debuggowanie i uruchamianie ====
 Uwagi praktycznie odnośnie debbugowania i podglądania pamięci: Uwagi praktycznie odnośnie debbugowania i podglądania pamięci:
Line 207: Line 363:
 </figure> </figure>
 ==== Metoda weryfikacji wyników ==== ==== Metoda weryfikacji wyników ====
-Teksty oraz okienka pojawiające się na ekranie zgadzają się z algorytmem. Zwróć szczególną uwagę na to, czy nie są generowane losowe ciągi znaków.+Teksty oraz okienka pojawiające się na ekranie zgadzają się z algorytmem. Zwróć szczególną uwagę na to, czy nie są generowane losowe ciągi znaków, co może sugerować, że zapomniałeś o kończącej łańcuch wartości 0.
 ==== Pomocne zasoby ==== ==== Pomocne zasoby ====
   * Visual Studio 2019: [[https://visualstudio.microsoft.com/vs/]]   * Visual Studio 2019: [[https://visualstudio.microsoft.com/vs/]]
   * Przydatne funkcje Windows API: [[https://docs.microsoft.com/en-us/windows/win32/api/_winmsg/]]   * Przydatne funkcje Windows API: [[https://docs.microsoft.com/en-us/windows/win32/api/_winmsg/]]
pl/edu/zmitac/n1z/ja/lab1.1615565239.txt.gz · Last modified: by pczekalski