=============================== ZajÄcia 2: wstawki assemblerowe =============================== Data: 03.03.2020 .. contents:: .. toctree:: :hidden: zadanie MateriaĹy dodatkowe =================== - :ref:`02-zadanie` - https://gcc.gnu.org/onlinedocs/gcc-8.3.0/gcc/Using-Assembly-Language-with-C.html Jak uĹźywaÄ assemblera w programach napisanych w C ================================================= JeĹli chcemy uĹźyÄ instrukcji assemblera w kodzie napisanym w jÄzyku C, mamy kilka moĹźliwoĹci: 1. UznaÄ, Ĺźe wcale nie chcemy dotykaÄ assemblera, napisaÄ przenoĹny kod i zaufaÄ optymalizatorowi. 2. UĹźyÄ rozszerzeĹ kompilatora niezaleĹźnych od architektury (``__vector__``, ``__builtin_popcount``, ...). 3. UĹźyÄ rozszerzeĹ kompilatora zaleĹźnych od architektury (tzw. intrinsics, np. ``_mm_aesenc_si128``). 4. UĹźyÄ wstawki assemblerowej (``__asm__``). 5. NapisaÄ caĹÄ funkcjÄ w assemblerze i wywoĹaÄ jÄ w C. Wstawki assemblerowe sÄ najbardziej delikatnym i niebezpiecznym z powyĹźszych mechanizmĂłw. Wstawki assemblerowe w gcc ========================== SkĹadnia wstawki assemblerowej jest zaleĹźna od kompilatora. W przypadku gcc i clanga jest ona nastÄpujÄ ca:: __asm__ <opcjonalnie volatile> ( "<kod assemblera>" : <lista wyjĹÄ> : <lista wejĹÄ> : <lista nadpisanych rejestrĂłw> ); W gcc, piszÄ c wstawkÄ assemblerowÄ efektywnie definiujemy nowÄ instrukcjÄ assemblera w kompilatorze, ktĂłra bÄdzie traktowana przez backend kompilatora na rĂłwni ze zwykĹymi. Tak jak w przypadku zwykĹych instrukcji, kompilator musi dokĹadnie znaÄ zachowanie naszej wstawki -- jej wejĹcia, wyjĹcia oraz moĹźliwoĹci optymalizacji. DomyĹlnie, kod wstawki podlega optymalizacjom przepĹywu danych -- gcc zakĹada, Ĺźe wstawka assemblera nie ma skutkĂłw ubocznych (innych niĹź zapisanie wyjĹÄ) i moĹźe usunÄ Ä wstawkÄ gdy jej wyjĹcia nie sÄ uĹźywane, przestawiÄ jÄ z innym kodem, bÄ dĹş zduplikowaÄ. Ĺťeby tego uniknÄ Ä, moĹźemy uĹźyÄ sĹowa ``volatile`` -- to zagwarantuje, Ĺźe nasza wstawka wykona siÄ dokĹadnie jeden raz, gdy zostanie uĹźywa. Kod assemblera we wstawce jest szablonem tekstu, ktĂłry zostanie wyemitowany bezpoĹrednio do pliku assemblerowego bÄdÄ cego wyjĹciem kompilatora. W tym szablonie gcc podstawi zaalokowane wejĹcia i wyjĹcia w odpowiednie miejsca. Listy wejĹÄ i wyjĹÄ opisujÄ dane, ktĂłrych bÄdzie uĹźywaÄ nasza wstawka. KaĹźdy wpis opisuje zmiennÄ (bÄ dĹş wyraĹźenie) w C, ktĂłra zostanie podstawiona w odpowiednim miejscu szablonu oraz ograniczenia na miejsce, w ktĂłrym powinna zostaÄ umieszczona -- np. ``"g"`` oznacza rejestr ogĂłlnego przeznaczenia, a ``"m"`` oznacza pamiÄÄ. Lista nadpisanych rejestrĂłw opisuje, co nasza wstawka modyfikuje jako skutki uboczne. MoĹźemy tam uĹźyÄ nazw rejestrĂłw, napisaÄ ``"cc"`` jeĹli nasza wstawka modyfikuje rejestr znacznikĂłw (``rflags`` i jego odpowiedniki), bÄ dĹş napisaÄ ``"memory"``, jeĹli nasza wstawka modyfikuje struktury w pamiÄci (inne niĹź te przekazane jako wyjĹcia). PrzykĹad (bezuĹźyteczny) wstawki assemblerowej:: int a, b, c; // a = b + c; __asm__ ( "addl %1, %0" : "=g"(a) : "gm"(b), "0"(c) : "cc" ); W tym wypadku mĂłwimy, Ĺźe gcc moĹźe umieĹciÄ ``b`` w rejestrze bÄ dĹş pamiÄci (dwa róşne ograniczenia), ``a`` musi umieĹciÄ w rejestrze, a ``c`` musi umieĹciÄ w tym samym rejestrze, co argument 0 (czyli a). Kompilator wykona odpowiedniÄ alokacjÄ rejestrĂłw i podstawi wybrane rejestry (bÄ dĹş adres pamiÄci) w miejsce ``%0`` i ``%1`` w trakcie emitowania kodu assemblera. Czasem potrzebujemy dokĹadniejszego ograniczenia lokalizacji wejĹcia/wyjĹcia do konkretnego rejestru (np. w przypadku syscalli). Ĺťeby to zrobiÄ, uĹźywamy ograniczenia ``"g"`` w poĹÄ czeniu ze zmiennÄ zdefiniowanÄ w nastÄpujÄ cy sposĂłb:: register int b __asm__("eax"); // a = b + c; __asm__ ( "addl %%eax, %0" : "=g"(a) : "g"(b), "0"(c) : "cc" ); Wstawki assemblerowe mogÄ zawieraÄ wiÄcej niĹź jednÄ instrukcjÄ (choÄ naleĹźy tego unikaÄ) -- oddzielamy je wtedy przez ``;`` bÄ dĹş ``\n`` w tekĹcie szablonu. JeĹli potrzebujemy zdefiniowaÄ etykietÄ wewnÄ trz wstawki, nie powinniĹmy nadawaÄ jej nazwy (bÄdzie ona kolidowaÄ, jeĹli kompilator postanowi zduplikowaÄ kod wstawki) -- zamiast tego, powinniĹmy uĹźyÄ etykiet lokalnych (https://sourceware.org/binutils/docs/as/Symbol-Names.html). Dla przykĹadu, wykonanie syscalla ``read`` na x86_64:: ssize_t read (int fd_, void *ptr_, size_t len_) { register int sys_nr __asm__("eax") = __NR_read; register ssize_t res __asm__("rax"); register int fd __asm__("edi") = fd_; register void *ptr __asm__("rsi") = ptr_; register size_t len __asm__("rdx") = len_; __asm__ volatile ( "syscall" : "=g"(res) : "g"(sys_nr), "g"(fd), "g"(ptr), "g"(len) : "cc", "rcx", "r11", "memory" ); return res; }