User Tools

Site Tools


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

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:lab3 [2021/04/07 14:21] – [Przekazywanie wskaźników z poziomi C# do funkcji w assemblerze] pczekalskipl:edu:zmitac:n1z:ja:lab3 [2022/04/23 16:03] (current) – [Konfiguracja środowiska] pczekalski
Line 1: Line 1:
-====== Integracja kodu niezarządzalnego, tworzonego w języku Assembler wraz z kodem zarządzalnym .NET C# ======+===== Ćwiczenie 3: Integracja kodu niezarządzalnego, tworzonego w języku Assembler wraz z kodem zarządzalnym .NET C# =====
  
-===== Ćwiczenie 3 ===== 
-Laboratorium języków assemblerowych, ćwiczenie 3 
  
 ==== Środowisko sprzętowe i programistyczne ==== ==== Środowisko sprzętowe i programistyczne ====
Line 41: Line 39:
  
 Konsekwencją powyższego faktu są przypadki, gdzie kod w assemblerze będzie "rzucał wyjątki" dostępu do pamięci i utraci (może utracić) dostęp do danych przekazanych z .NET, ponieważ (rys. {{ref>gc1}}, {{ref>gc3}} i {{ref>gc2}}): Konsekwencją powyższego faktu są przypadki, gdzie kod w assemblerze będzie "rzucał wyjątki" dostępu do pamięci i utraci (może utracić) dostęp do danych przekazanych z .NET, ponieważ (rys. {{ref>gc1}}, {{ref>gc3}} i {{ref>gc2}}):
-  * W czasie przetwarzania danych przez funkcję w assemblerze, Garbage Collector wykonał cykl porządkowania, wykrył, że w kodzie .NET nie ma dalszych odwołań do zmiennej, skutkiem czego usunął ją i zwolnił pamięc po stronie kodu wywołującego w środowisku .NET; Kod funkcji w assemblerze utraci dostęp do danych i w konsekwencji pojawi się wyjątek odczytu/zapisu pamięci, ponieważ pod wskaźnikiem, który znajduje się po stronie kodu w assemblerze, nie ma już danych, na które ten wskaźnik wskazywał;+  * W czasie przetwarzania danych przez funkcję w assemblerze, Garbage Collector wykonał cykl porządkowania, wykrył, że w kodzie .NET nie ma dalszych odwołań do zmiennej, skutkiem czego usunął ją i zwolnił pamięć po stronie kodu wywołującego w środowisku .NET; Kod funkcji w assemblerze utraci dostęp do danych i w konsekwencji pojawi się wyjątek odczytu/zapisu pamięci, ponieważ pod wskaźnikiem, który znajduje się po stronie kodu w assemblerze, nie ma już danych, na które ten wskaźnik wskazywał;
   * W czasie przetwarzania danych przez funkcję w assemblerze, Garbage Collector wykonał cykl porządkowania i przesunął zmienną w inne miejsce w pamięci z powodów optymalizacyjnych; Kod w assemblerze nie otrzyma "aktualizacji" wskaźnika, ponieważ jest poza jurysdykcją środowiska .NET, skutkiem czego nastąpi błąd odczytu/zapisu pamięci, ponieważ wykonując dereferencję wskażnika trafimy w miesce w pamięci, które nie przechowuje już zmiennej (jest wolne lub zajęte przez inną strukturę danych, po stronie .NET);   * W czasie przetwarzania danych przez funkcję w assemblerze, Garbage Collector wykonał cykl porządkowania i przesunął zmienną w inne miejsce w pamięci z powodów optymalizacyjnych; Kod w assemblerze nie otrzyma "aktualizacji" wskaźnika, ponieważ jest poza jurysdykcją środowiska .NET, skutkiem czego nastąpi błąd odczytu/zapisu pamięci, ponieważ wykonując dereferencję wskażnika trafimy w miesce w pamięci, które nie przechowuje już zmiennej (jest wolne lub zajęte przez inną strukturę danych, po stronie .NET);
   * Inne przypadki: np. asynchroniczny wątek aplikacji w środowisku .NET zmodyfikuje zmienną i np. skróci tablicę zmiennych, co może skutkować błędami po stronie kodu w assemblerze, w szczególności błędami logicznymi i błedami indeksacji i/lub wyjątkami związanymi z dostępem do pamięci;   * Inne przypadki: np. asynchroniczny wątek aplikacji w środowisku .NET zmodyfikuje zmienną i np. skróci tablicę zmiennych, co może skutkować błędami po stronie kodu w assemblerze, w szczególności błędami logicznymi i błedami indeksacji i/lub wyjątkami związanymi z dostępem do pamięci;
Line 64: Line 62:
   * w celu poinformowania Garbage Collectora o zakazie relokacji zmiennej na czas wywołania funkcji w assemblerze należy użyć słowa kluczowego ''fixed'' po stronie kodu .NET (C#) ((https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/fixed-statement)) - fixed należy używać równolegle z ''unsafe'';    * w celu poinformowania Garbage Collectora o zakazie relokacji zmiennej na czas wywołania funkcji w assemblerze należy użyć słowa kluczowego ''fixed'' po stronie kodu .NET (C#) ((https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/fixed-statement)) - fixed należy używać równolegle z ''unsafe''; 
  
-<note important>Błędy związane z brakiem zastosowania powyższych reguł (relokacją pamięci przez Garbage Collector i następującym po nim odwołaniem z poziomu języka assembler) są niezwykle trudne do wykrycia, ponieważ aplikacja zachowuje się nieprzewidywalnie: raz działa, innym razem nie. Wynika to z faktu, że Garbage Collector może, ale nie musi przesunąć zmiennej, zatem kod w takim przypadku może działać "przez przypadek", ponadto jego uruchomienie jest zależne od wielu czynników, w tym np. od obciążenia pamięci operacyjnej przez inne procesy, co oznacza że dwukrotne uruchomienie aplikacji może powodować różne działanie.</note>+<note warning>Błędy związane z brakiem zastosowania powyższych reguł (relokacją pamięci przez Garbage Collector i następującym po nim odwołaniem z poziomu języka assembler) są niezwykle trudne do wykrycia, ponieważ aplikacja zachowuje się nieprzewidywalnie: raz działa, innym razem nie. Wynika to z faktu, że Garbage Collector może, ale nie musi przesunąć zmiennej, zatem kod w takim przypadku może działać "przez przypadek". Działanie ''Garbage Collectora'' jest zależne od wielu czynników, w tym np. od obciążenia pamięci operacyjnej przez inne procesy, co oznacza że dwukrotne uruchomienie aplikacji może powodować jej różne działanie.</note>
  
 ==== Dołączanie biblioteki DLL do kodu w C# ==== ==== Dołączanie biblioteki DLL do kodu w C# ====
Line 95: Line 93:
 <note important>Uwaga! Należy zapewnić, aby skompilowana biblioteka DLL (tutaj ''DLLAsm.dll'') znalazła się w tym samym folderze, w którym znajdzie się skompilowana aplikacja .NET. W przeciwnym wypadku podczas uruchamiania zostanie rzucony wyjątek o braku możliwości zainstalowania biblioteki. W kolejnym rozdziale znajduje się infomacja, jak skonfigurować projekty, aby generowały wyniki do wspólnego folderu.</note> <note important>Uwaga! Należy zapewnić, aby skompilowana biblioteka DLL (tutaj ''DLLAsm.dll'') znalazła się w tym samym folderze, w którym znajdzie się skompilowana aplikacja .NET. W przeciwnym wypadku podczas uruchamiania zostanie rzucony wyjątek o braku możliwości zainstalowania biblioteki. W kolejnym rozdziale znajduje się infomacja, jak skonfigurować projekty, aby generowały wyniki do wspólnego folderu.</note>
  
-<note tip>Można również pozostawić bibliotekę w pierwotnej lokalizacji o zmodyfikować dyrektywę ''DLLImport'' podając ścieżkę dostępu przed nazwą pliku DLL, względną lub absolutną. W przypadku użycia tej ostatniej, nie będzie można zmienić lokalizacji biblioteki.</note>+<note tip>Można również pozostawić bibliotekę w pierwotnej lokalizacji i zmodyfikować dyrektywę ''DLLImport'' podając ścieżkę dostępu przed nazwą pliku DLL, względną lub absolutną. W przypadku użycia tej ostatniej, nie będzie można zmienić lokalizacji biblioteki, a w przypadku skopiowania biblioteki w inne miejsce na dysku, przestanie ona działać.</note>
  
 Uwaga na przekazywane i zwracane typy! Informacja o tym, gdzie znaleźć zmienne po stronie wywoływanej funkcji znajduje się w opisie do [[pl:edu:zmitac:n1z:ja:lab1|Laboratorium JA ćw. 1]].\\ Uwaga na przekazywane i zwracane typy! Informacja o tym, gdzie znaleźć zmienne po stronie wywoływanej funkcji znajduje się w opisie do [[pl:edu:zmitac:n1z:ja:lab1|Laboratorium JA ćw. 1]].\\
Line 180: Line 178:
 <note warning>Przed wykonaniem pierwszej kompilacji folder nie zostanie założony, zatem nie będzie można go wybrać za pomoca przycisku [Browse...]. W związku z tym należy wykonać pustą kompilację wyłącznie projektu DLL w celu utworzenia folderu wynikowego.</note> <note warning>Przed wykonaniem pierwszej kompilacji folder nie zostanie założony, zatem nie będzie można go wybrać za pomoca przycisku [Browse...]. W związku z tym należy wykonać pustą kompilację wyłącznie projektu DLL w celu utworzenia folderu wynikowego.</note>
 <note warning>Powyższa konfiguracja zapisuje na stałe ścieżkę w konfiguracji projektu. Oznacza to, że zmiana z trybu Debug na Release wymaga zmiany ww. ścieżki!</note> <note warning>Powyższa konfiguracja zapisuje na stałe ścieżkę w konfiguracji projektu. Oznacza to, że zmiana z trybu Debug na Release wymaga zmiany ww. ścieżki!</note>
 +
 +W przypadku, gdyby pomimo ustawienia wspólnego folderu wynikowego nadal były zgłaszane błędy braku biblioteki DLL, istnieje możliwość, że włączony jest system tworzenia dodatkowych podfolderów, jak to opisano poniżej w notce z uwagami. Aby usunąć ten problem, konieczne jest ręczna edycja pliku konfiguracyjnego projektu.\\ Aby otworzyć plik konfiguracyjny projektu, należy dwukrotnie klinąć nazwe projektu w drzewie. Otworzy się plik typu XML, z rozszerzeniem ''.csproj''.
 +
 +<note warning>Uwaga! W przypadku projektu WPF w folderze wynikowym może pojawić się podfolder z wersją frameworku .NET, co w praktyce spowoduje, że plik wykonywalny z frontside (GUI) zostanie uruchomiony w innym folderze (w podfolderze) w stosunku do wybranego w ustawieniach, skutkiem czego biblioteka DLL nie zostanie załadowana i zostanie zgłoszony wyjątek. Podfoldery te są dodawane automatycznie. Aby usunąć tę niedogodność, w pliku projektu WPF należy mieścić dodatkową sekcję((https://docs.microsoft.com/en-us/visualstudio/ide/how-to-change-the-build-output-directory?view=vs-2019)):
 +<code xml>
 +
 +<PropertyGroup>
 +  <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
 +  <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
 +</PropertyGroup>
 +
 +</code>
 +</note>
  
 === Konfiguracja na potrzeby kodu unsafe oraz debuggowania === === Konfiguracja na potrzeby kodu unsafe oraz debuggowania ===
 Do ustawienia pozostają jeszcze dwie opcje: Do ustawienia pozostają jeszcze dwie opcje:
   * Włączenie użycia kodu "unsafe", o ile dyrektywa "unsafe" jest wymagana. Dyrektywy tej nie trzeba używać m.in. w przypadku wykorzystywania struktury ''IntPtr'' do przekazywania wskaźników oraz w przypadku przekazywania argumentów przez wartość, niemniej jednak zalecane jest jej włączenie. W tym celu we właściwościach projektu w języku C# należy w sekcji "Build" w grupie"General" zaznaczyć opcję "Allow unsafe code" (rysunek {{ref>asm3_13}});   * Włączenie użycia kodu "unsafe", o ile dyrektywa "unsafe" jest wymagana. Dyrektywy tej nie trzeba używać m.in. w przypadku wykorzystywania struktury ''IntPtr'' do przekazywania wskaźników oraz w przypadku przekazywania argumentów przez wartość, niemniej jednak zalecane jest jej włączenie. W tym celu we właściwościach projektu w języku C# należy w sekcji "Build" w grupie"General" zaznaczyć opcję "Allow unsafe code" (rysunek {{ref>asm3_13}});
-  * Włączenie debuggowania kodu natywnego, co pozwoli na płynne przełączanie pomiędzy kodem wywołującym w C# i wywoływanym w assemblerze w jednej sesji debuggera. W tym celu we właściwościach projektu w jezyku C# należy wybrać sekcję "Debug", a następnie w grupie opcji "Debugger engines" zaznaczyć opcję "Enable natie code debugging" (rysunek {{ref>asm3_14}});+  * Włączenie debuggowania kodu natywnego, co pozwoli na płynne przełączanie pomiędzy kodem wywołującym w C# i wywoływanym w assemblerze w jednej sesji debuggera. W tym celu we właściwościach projektu w jezyku C# należy wybrać sekcję "Debug", a następnie w grupie opcji "Debugger engines" zaznaczyć opcję "Enable natie code debugging" (rysunki {{ref>asm3_14}},{{ref>asm3_14_2}});
  
 <figure asm3_13> <figure asm3_13>
Line 192: Line 203:
 <figure asm3_14> <figure asm3_14>
 {{:pl:edu:zmitac:n1z:ja:virtualbox_msedge_-_win10_06_04_2021_22_48_59.png?600|}} {{:pl:edu:zmitac:n1z:ja:virtualbox_msedge_-_win10_06_04_2021_22_48_59.png?600|}}
-<caption>Włączenie debuggowania kodu niezarządzalnego</caption>+<caption>Włączenie debuggowania kodu niezarządzalnego (VS do wersji 2019 włącznie, .NET do wersji 5)</caption> 
 +</figure> 
 +<figure asm3_14_2> 
 +{{:pl:edu:zmitac:n1z:ja:virtualbox_msedge_-_win10_-_ja_nsi_23_04_2022_16_00_45.png?600|}} 
 +<caption>Włączenie debuggowania kodu niezarządzalnego (VS od wersji 2022 włącznie, .NET 6.0)</caption>
 </figure> </figure>
  
-==== Przekazywanie wskaźników z poziomi C# do funkcji w assemblerze ====+==== Przekazywanie wskaźników z poziomu C# do funkcji w assemblerze ====
 Przekazywanie typów złożonych (tablic, obiektów, struktur) jest możliwe wyłącznie za pomocą wskaźników. Wskaźniki te są przekazywane jako typy int, 64-bitowe, w rejestrach. Ich interpretacja po stronie funkcji wywoływanej należy do programisty. Nieniej jednak po stronie języka C# nie jest to tak oczywiste.  Przekazywanie typów złożonych (tablic, obiektów, struktur) jest możliwe wyłącznie za pomocą wskaźników. Wskaźniki te są przekazywane jako typy int, 64-bitowe, w rejestrach. Ich interpretacja po stronie funkcji wywoływanej należy do programisty. Nieniej jednak po stronie języka C# nie jest to tak oczywiste. 
  
Line 223: Line 238:
 W powyższym przykładzie pominięto przekazywanie do funkcji długości tablicy, gdyż jej rozmiar (6 elementów) jest z góry znany. W przypadku, gdy rozmiar tablicy jest zmienny, należy przekazać do funkcji w assemblerze jej długość jako osobny parametr. W powyższym przykładzie pominięto przekazywanie do funkcji długości tablicy, gdyż jej rozmiar (6 elementów) jest z góry znany. W przypadku, gdy rozmiar tablicy jest zmienny, należy przekazać do funkcji w assemblerze jej długość jako osobny parametr.
  
-Przykładowy kod w assemblerze, który zwraca wskazany element tablcy może wyglądać następująco:+<note important>Uwaga! Tablice po stronie kodu w C# są alokowane liniowo. Ponieważ jednak w C# nie da się w prosty sposób otrzymać wskaźnika do obiektu, który nie jest typem prostym (a tablica nim nie jest), używamy pewnego obejścia polegającego na tym, że pobieramy adres do pierwszego elementu tablicy, zamiast do całego obiektu tablicy. Ostatecznie, w przypadku tablicy typów prostych, adresy będą takie same. Elementy pojedynczej tablicy są alokowane bez wyrównania do 64 bitów. Dopiero po ostatnim elemencie może pojawić się wyrównanie (o ile będzie konieczne). Wyrównanie widać w kolejnym przykładzie, gdzie korzystamy ze struktury.</note> 
 + 
 +Przykładowy kod w assemblerze, który zwraca wskazany element tablcy, może wyglądać następująco:
 <code asm> <code asm>
 ProcAsm2 proc ProcAsm2 proc
Line 242: Line 259:
 Na potrzeby poniższego przykładu przyjmujemy, że chcemy przekazać do assemblera trzy tablice typu ''byte'' o różnich długościach. Na potrzeby poniższego przykładu przyjmujemy, że chcemy przekazać do assemblera trzy tablice typu ''byte'' o różnich długościach.
  
-<note important>W przypadku, gdyby liczba parametrów, które trzeba przekazać do funkcji przekraczała 4, aby nie musieć umieszczać ich na stosie, można po stronie C# zadeklarować strukturę zawierającą argumenty funkcji, a następnie przekazać do funkcji w assemblerze wskaźnik na tę strukturę, zamiast każdy z argumentów osobno. Po stronie assemblera należy wtedy tę strukturę "rozkodować"</note>+<note tip>W przypadku, gdyby liczba parametrów, które trzeba przekazać do funkcji przekraczała 4, aby nie musieć umieszczać ich na stosie, można po stronie C# zadeklarować strukturę zawierającą argumenty funkcji, a następnie przekazać do funkcji w assemblerze wskaźnik na tę strukturę, zamiast każdy z argumentów osobno. Po stronie assemblera należy wtedy tę strukturę "rozkodować"</note>
  
 Deklaracja struktury po stronie języka C# może wyglądać następująco (ważna jest kolejność zmiennych, bo w takiej kolejności będą się one potem pojawiały w pamięci): Deklaracja struktury po stronie języka C# może wyglądać następująco (ważna jest kolejność zmiennych, bo w takiej kolejności będą się one potem pojawiały w pamięci):
Line 263: Line 280:
 <note important>Uwaga. Struktura MUSI byż oznaczona jako ''unsafe'', ponieważ zawiera typy wskaźnikowe ''byte*''</note> <note important>Uwaga. Struktura MUSI byż oznaczona jako ''unsafe'', ponieważ zawiera typy wskaźnikowe ''byte*''</note>
  
-<note warning>Dane wewnątrz struktury (zmienne) są "wyrównanie" do 8 bajtów (64 bitów). A zatem każdy z ww elementów składowych struktury zajmuje w pamięci 8 bajtów nawet, jeśli długość zmiennej na to nie wskazuje.</note>+<note warning>Dane wewnątrz struktury (zmienne) są "wyrównanie" do 8 bajtów (64 bitów). A zatem każdy z ww elementów składowych struktury zajmuje w pamięci 8 bajtów nawet, jeśli długość zmiennej na to nie wskazuje. Przykładowo pojedynczy, ''byte'' (zmienna) zajmuje w pamięci również 8 bajtów, z czego znaczący jest tylko pierwszy, najmłodszy. Oczywiście w przypadku pojedynczej zmiennej typu tablicowego, dane są uszeregowane w ciągu, bez odstępu, zatem przykładowo ''byte[8]'' zajmuje 8 bajtów pod rząd, bez żadnego odstępu i wyrównywania.</note>
  
 Na potrzeby przykładu zostały tez utworzone 3 tablice bajtów oraz instancja ww. struktury typu ''StructToAssembler''. Deklaracja ich jako statycznych wynika z faktu iż dane zostały zadeklarowane wewnątrz klasy statycznej ''Program'' odpowiadającej za przykładowy kod aplikacji typu CommandLine w języku C# (w innym kontekście, tablica bajtów może być np. skonwertowaną listą wartości RGB z bitmapy, danymi numerycznymi ładowanymi z pliku lub dowolna, inna forma, w tym może być zadeklarowana dynamicznie (vide ''a3''): Na potrzeby przykładu zostały tez utworzone 3 tablice bajtów oraz instancja ww. struktury typu ''StructToAssembler''. Deklaracja ich jako statycznych wynika z faktu iż dane zostały zadeklarowane wewnątrz klasy statycznej ''Program'' odpowiadającej za przykładowy kod aplikacji typu CommandLine w języku C# (w innym kontekście, tablica bajtów może być np. skonwertowaną listą wartości RGB z bitmapy, danymi numerycznymi ładowanymi z pliku lub dowolna, inna forma, w tym może być zadeklarowana dynamicznie (vide ''a3''):
Line 337: Line 354:
 <caption>Ułożenie zmiennej ''struktura'' i jej składowych w pamięci operacyjnej</caption> <caption>Ułożenie zmiennej ''struktura'' i jej składowych w pamięci operacyjnej</caption>
 </figure> </figure>
 +
 +Wskaźnik został przekazany przez rejestr ''rcx''.\\
 +W okienku [Memory 1] dane prezentowane są w postaci liczb 64-bitowych (wskaźniki oraz ''int'' wyrównany do 64 bitów), odpowiedno w kolejności zgodnej z deklaracją typu ''StructToAssembler'', a zatem jest to w kolejności:
 +  * wskaźnik na tablicę ''a1'' (adres: 0x000002495fad2e90), czyli wartość przypisana za pomocą ''struktura.ptrA1 = ptr1;'';
 +  * rozmiar tablicy ''a1'' (8 elementów), czyli wartość pola ''struktura.lenA1'';
 +  * wskaźnik na tablicę ''a2'' (adres: 0x000002495fad2ef8), czyli wartość przypisana za pomocą ''struktura.ptrA2 = ptr2;'';
 +  * rozmiar tablicy ''a2'' (6 elementów), czyli wartość pola ''struktura.lenA2'';
 +  * wskaźnik na tablicę ''a3'' (adres: 0x000002495fad2f60), czyli wartość przypisana za pomocą ''struktura.ptrA3 = ptr3;'';
 +  * rozmiar tablicy ''a3'' (10 elementów, zapisane szesnastkowo jako '0x0a'), czyli wartość pola ''struktura.lenA3'';
 +Uwaga - przy każdym uruchomieniu aplikacji te adresy będą (mogą być) inne, ponieważ pamięć jest alokowana dynamicznie. Natomiast klauzula ''fixed'' powoduje, że podczas wywołania procedury ''ProcAsm3(aAddress);'' wartości te nie ulegną zmianie.
 +
 +== Podgląd pamięci ==
 +Na rysunku {{ref>asm3_20}} w okienku [Memory 2] widać zawartość pamięci odpowiadającej zmiennej ''a1'', prezentowaną w postaci liczb dziesiętnych bez znaku, o długości bajtowej, zgodnie z tym, co zostało zadeklarowane po stronie języka C# (jest to w kolejności 2,4,6,8,10,12,14,16). Analogicznie [Memory 3] dla zmiennej ''a2'' i [Memory 4] dla zmiennej ''a3''.
 ==== Zadanie do wykonania ==== ==== Zadanie do wykonania ====
 W ramach laboratorium studenci zrealizują prostą aplikację konsolową, działającą w środowisku .NET w języku C#, która będzie wywoływała funkcje obliczeniowe, utworzone w kodzie niezarządzalnym, w języku Assembler, w postaci biblioteki dynamicznie dołączanej (DLL). Zmienne będą zadeklarowane po stronie .NET. W ramach laboratorium studenci zrealizują prostą aplikację konsolową, działającą w środowisku .NET w języku C#, która będzie wywoływała funkcje obliczeniowe, utworzone w kodzie niezarządzalnym, w języku Assembler, w postaci biblioteki dynamicznie dołączanej (DLL). Zmienne będą zadeklarowane po stronie .NET.
  
 W szczególności, do wykonania są następujące zadania: W szczególności, do wykonania są następujące zadania:
-  - Zsumowanie wektora danych typu uint (zadeklarowanych po stronie ,NET w postaci tablicy jednowymiarowej o wymiarze 20 zmiennych). Funkcja ma zwracać pojedynczą wartość (przez rejestr). +  - Zsumowanie wektora danych typu uint (zadeklarowanych po stronie ,NET w postaci tablicy jednowymiarowej o wymiarze 20 zmiennych). Funkcja ma zwracać pojedynczą wartość (przez rejestr). Wskaźnik na tablicę źródłową oraz jej długość należy podać do funkcji w assemblerze poprzez rejestry. 
-  - Obliczenie wartości średnich dla zestawu zmiennych przechowywanych w postaci dwóch wektorów o długości 16, typu float, pozycja po pozycji, wynikiem ma być wektor średnich (o długości 16). Funkcja nie zwraca żadnej wartości. Wyniki są umieszczane w tablicy zadeklarowanej po stronie ,NET i przekazanej do funkcji jako wskaźnik.+  - Obliczenie wartości średnich dla zestawu zmiennych przechowywanych w postaci dwóch wektorów o długości 16, typu float, pozycja po pozycji, wynikiem ma być wektor średnich (o długości 16). Funkcja nie zwraca żadnej wartości. Wyniki są umieszczane w tablicy zadeklarowanej po stronie ,NET i przekazanej do funkcji jako wskaźnik. Sugerowane jest użycie struktury do przekazania danych do funkcji. Docelową tablicę należy zadeklarować po stronie języka C#. Zalecane jest wykonanie operacji za pomocą pętli, tj. obliczanie osobno dla każdej pary. Można skorzystać albo z funkcji koprocesora arytmetycznego, albo z MMX/SSE.
  
 Powyższe operacje są od siebie niezależne, a zatem powinny być zrealizowane w ramach dwóch, niezależnych funkcji assemblerowych. Powyższe operacje są od siebie niezależne, a zatem powinny być zrealizowane w ramach dwóch, niezależnych funkcji assemblerowych.
pl/edu/zmitac/n1z/ja/lab3.1617798074.txt.gz · Last modified: by pczekalski