Asembler / kod bajtowy - czy potrzebny i czy warto?

1

Pytanie 1. Czy potrzebna Wam ta wiedza na te tematy i/lub umiejetnosc programowania w asemblerze w pracy / projektach open-source/ wlasnych projektach?
Pytanie 2. Czy warto samemu napisac kompilator czy lepiej moze interpreter bo tam chyba nie powinno sie uzywac asemblera / kodu bajtowego?
Pytanie 3. A moze lepiej wcale nie pisac kompilatora / interpretera bo to i tak wcale albo prawie wcale nieprzydatne?

Jestem raczej typem badacza niz kreatora - wole jeden program napisac na wiele sposobow aby badac dany jezyk programowania, uzyc innego algorytmu, struktury danych, aby z programu ktory pisze jak najwiecej czasu poswiecic na kwestie informatyczne a nie na rozwiazanie problemu poza-informatycznego z innej dziedziny nauki (np. matematyki, ekonomii, fizyki, itd.). Bardzo interesuje mnie co sie dzieje na poziomie maszyny - czyli asembler lub kod bajtowy, ale jednoczesnie wiem ze to czasochlonne i chyba nieprzydatne i dlatego mam wahania co do wyboru programu do napisania.

Pytanie 4. Takze pisanie kompilatora mogloby zaangazowac tylko mala czesc jezyka programowania i algorytmow a caly asembler czy kod bajtowy - to juz lepiej chyba pisac interpreter w C/C++/Javie takiego Pythona/basha/PHP/ (a nawet i C90 - interpreter C90 tez moglby powstac? i na odwrot kompilator basha tez ? - choc byloby to bardzo trudne), bo sa bardziej przydatne od asemblera a takze latwiej byloby testowac taki interpreter ?

Hipotetyczne przypadki w Linuxie:
Case 'A': Cezary pisze caly kompilator w C90 aby moc kompilowac pliki C90. Po wielu bojach jest moment ze chce testowac plik one.c:

#include <stdio.h>
int main (void) {
   int x = 5;
   printf ("Hello World! %d", x);
   return 0;
}

Zalozmy ze napisal caly kompilator w 1 pliku: translator.c
wykonywalny plik: 'translator' za pomoca polecenia: gcc -Wall translator.c -o translator
teraz aby skompilowac plik one.c do asemblera w konsoli pisze: .\translator one.c (jako odpowiednik gcc -S one.c) i otrzymuje plik one.s
Aby uzyskac relokowany plik obiektowy za pomoca asemblera w konsoli pisze np.: as one.s -o one.o (dla innego asemblera np nasm byloby to inne polecenie)

Pytanie 5 i dla mnie bardzo wazne:
Jezeli juz mam plik obiektowy z instrucjami asemblera to moge go teraz uruchomic tym asemblerem w celu przetestowania tego pliku, aby pojawil sie w konsoli napis: 'Hello World! 5'
???

1

W pracy nigdy nie był mi potrzebny i na 99% nie będzie. Niemniej w prywatnych projektach aka reverse engineering (gry) to jest podstawa podstaw stąd czasami do niego przysiąde.

1

Chyba nawet Prince of persja jest napisany w ASM i koles mial ze 20 cos lat, nie wspoaminajac ze internet raczkowal

1

Może zamiast rozkminiać w nieskończoność napisz wreszcie ten intepreter? ;)

  1. na początek nie warto kompilować istniejących języków, bo się zagłębisz w specyfikacjach i problemach z parsowaniem trudnej składni
  2. wymyśl sobie banalny język z jakąś prostą składnią (np. wzorowaną na asemblerze albo na lispie), tak żeby parsowanie tego było łatwe np. program z linijkami w stylu:
komenda argument
  1. łatwy model działania (np. niech to będzie stack machine, czyli, żeby dodać kilka liczb robisz tak (pseudokod):
umieść 100 // umieszcza na stosie 100
umieść 23 // umieszcza na stosie 23
dodaj // pobiera 2 liczby ze stosu(23 oraz 100) i dodaje, potem umieszcza wynik (123) na stosie
umieść 3 // umieszcza 3 na stosie
dodaj // zdejmuje 3 oraz 123 ze stosu, dodaje, umieszcza 126 na stosie
  1. i powyższe sobie możesz umieścić w kodzie bajtowym np. na zasadzie, że masz tam po kolei identyfikator komendy i argument, identyfikator kolejnej komendy i argument itp. np.: (zakładając, że 1 to umieść, 2 to dodaj)
    [1, 100, 1, 23, 2, 0, 1, 3, 2, 0]
  2. a później możesz przelatywać przez tablicę i sprawdzać (nawet za pomocą głupiego switch/case), co masz zrobić, jak trafisz na dany identyfikator.

I jeszcze dzisiaj możesz to zrobić.

Później możesz robić bardziej zaawansowane rzeczy, ale mam wrażenie, że w tej chwili chcesz coś zrobisz, a nie wiesz, na ile to będzie łatwe/trudne. Więc może lepiej zrobić jakiś PoC, a później się zastanowić, czy chcesz wejść w to głębiej.

na odwrot kompilator basha tez ? -

Składnia basha to tak jakby kot usiadł na klawiaturze :D

3

Proponuję lekturę. @Gynvael Coldwind robi coś podobnego w "Zrozumieć programowanie". Może przeczytanie z przykładami da Ci lepszy pogląd na sprawę niż klepanie jakiegoś kodu troszkę na oślep.
Link przykładowy do książki //ksiegarnia.pwn.pl/Zrozumiec-programowanie,688790689,p.html

0

Ostatnie 2 case:
Hipotetyczne przypadki w Linuxie:
Case 'B': Jowian pisze caly kompilator do kodu bajtowego Javy w Javie w 1 pliku translator.java (ale moglby w innym jezyku np C w pliku translator.c, tylko zeby ten translator potem kompilowac musialby uzyc gcc, a programy kompilowane za pomoca tego translatora w C i tak musialby uruchamiac poleceniem 'java' bo bylyby kompilowane do kodu bajtowego ?). Pisze kompilator (nie interpreter) jezyka C90 (tak, kompilator jezyka C90 w Javie). Po wielu bojach jest moment ze chce testowac plik one.c taki sam jak Cezary z innym kompilatorem napisanym w C90 tez dla C90:
za pomoca polecenia: javac -Xlint translator.java otrzymuje plik 'translator.class'
Plik translator.class jest skompilowanym plikiem utworzonym przez kompilator Java. Zawiera kod bajtowy, który jest binarnym kodem programu wykonywalnym, gdy jest uruchamiany przez wirtualną maszynę Java (JVM).
Pytanie 6:
teraz aby uruchomic swoj translator w celu skompilowania pliku one.c w konsoli pisze: java translator one.c ???
i po kompilacji do kodu bajtowego otrzymuje plik one.class
Pytanie 7:
teraz wystarczy testowac translator za pomoca polecenia: java one ???

Case 'C': Pankracy pisze caly interpreter w C++ w 1 pliku interpreter.cpp. Pisze interpreter jezyka C90 (tak, interpreter jezyka C90 w C++). Po wielu bojach jest moment ze chce testowac plik one.c taki sam jak jego poprzednicy:
za pomoca polecenia: g++ -Wall interpreter.cpp -o interpreter
otrzymuje plik wykonywalny o nazwie interpreter
Pytanie 8:
teraz wystarczy testowac interpreter po prostu uruchamiajac ten interpreter za pomoca polecenia:
./interpreter one.c ???

2

Ad.1 Zależy co piszesz i robisz. Ja mam background embeddowy, ASMa używałem dość dużo. Koledzy Javowcy - pewnie nigdy.
Ad. 2 nie wiem co Ci powiedzieć. Wiele osób w życiu nie napisało żadnego z powyższych i dobrze się w zawodzie mają.
Sam w trakcie studiów napisałem prosty asembler do procka własnego projektu (bardziej jako tool pomocniczy na dowód, że proc działa ;) ), w pracy parsowałem jakieś proste DSLe.
Ad. 3 Czy się do przydaje? Przydaje się lub nie, a czasem może ;) sam co jakiś czas robię commity do OSa, gdzie kompilator DSLa występuje, ale też nigdy nie miałem potrzeby niczego w nim zmieniać. Ja nie wiem co chcesz robić.
Ad.4 Obczaj architekturę LLVM czy emscripten.
Ad. 5 A linkowanie to gdzie? Albo zacznijmy od tego, co rozumiesz przez "plik obiektowy"?
Ad.6 i 7: nie rozumiem o co Ci chodzi, pytasz o strategię testowania czy o co w zasadzie?

0

@alagner:

alagner napisał(a):

Ad. 5 A linkowanie to gdzie? Albo zacznijmy od tego, co rozumiesz przez "plik obiektowy"?
Ad.6 i 7: nie rozumiem o co Ci chodzi, pytasz o strategię testowania czy o co w zasadzie?

Plik obiektowy to rodzaj pliku obiektowego generowanego przez kompilator i asembler podczas kompilacji pliku z kodem źródłowym - mialem chyba niepoprawny pomysl uruchomic plik obiektowy za pomoca asemblera ? W takim razie jesli za pomoca asemblera nie moglbym uruchomic tych obiektowych plikow to musialbym pewnie skorzytac z linkera ld lub kompilatora gcc ktory tego linkera wywolalby ?
Chodzi mi o to czy te polecenia w konsoli bylyby prawidlowe aby kompilowac pliki za pomoca mojego translatora lub uruchamiac pliki przez moj translator skompilowane. To samo dotyczy interpretera.

1

Prościej będzie zrobić sobie taką architekturę:

Translator C89 -> Nasm (taki assembler), i samo assemblowanie robić Nasm'em.

Tutaj dochodzi jeszcze problem formatu plików wykonywalnych, w Linuchu to ELF, w Windowsie PE. Jak chcesz pisać OS to bin, czyli instrukcje jedna za drugą.
Generalnie napisanie kompilatora bez optymalizacji jest proste. Pytanie czy robić to na x86 czy może arm (raspberry pi) - IMHO x86 to staroć, lepiej się uczyć ARMa.

Uważam że kompilator lub interpreter to bardzo fajny projekt. Tutaj przykład, gość będzie z tego książkę robił: https://github.com/rui314/chibicc
Obowiązkowa pozycja to: https://www.empik.com/kompilatory-reguly-metody-i-narzedzia-aho-alfred-v-ullman-jeffrey-lam-monica-s-sethi-ravi,p1233822786,ksiazka-p czyli "dragon book".

Generalnie mógłbym na ten temat jeszcze i stronę textu napisać, temat dla mnie bardzo ciekawy...

3

TL;DR jakbym dzisiaj pisał jakiś język to raczej bym targetował LLVM niż ASM, dzięki temu masz za darmo wykonywanie na n platformach.

0

@0xmarcin:
https://github.com/rui314/chibicc/blob/main/chibicc.h - w tym pliku sa zawarte wszystkie pliki naglowkowe, ktorych uzywa ten kompilator. Pisze to dlatego bo chcialbym "upiec wiele pieczeni na jednym ogniu" - czyli dzieki pisaniu kompilatora zapoznac sie z mozliwie jak najwieksza iloscia jezyka programowania i jego biblioteki a takze systemu operacyjnego (a najlepiej wielu jezykow ale tych oficjalnie uzywanych a nie swojego zmyslonego wlasnego albo niepraktycznego jak Brainfuck) a takze jak najwieksza iloscia
algorytmow i struktur danych, wzorcow projektowych. Nie chce juz pisac programow zwiazanych z ekonomia, fizyka i innymi nieinformatycznymi dziedzinami. Wolalbym napisac program bardzo informatyczny i do tego wielki objetosciowo aby nauczyc sie "nawigacji" i projektowania takiego programu. Waham sie miedzy kompilatorem a innymi opcjami i chyba lepszy bylby interpreter bo ten asembler / kod bajtowy to bardzo rzadko przydaje sie w praktyce ?

0

Tak assembler się bardzo rzadko (wcale?) przydaje w praktyce, zwłaszcza w pracy. Interpreter jest też ciekawy bo trzeba tam myśleć o zarządzaniu pamięcią (reference counting vs real GC). Można też pisać OS. Tyle że ostrzegam te projekty to nie są tydzień czy dwa, raczej 3 - 6 miechów intensywnej pracy jak zaczynasz od zera. Także pytanie na ile masz staminy....

0

Nie wyobrażam sobie programowania w języku C, bez znajomości ASM - gdy głównym tematem jest OSDev.

Podam przykład, bo bez tego się nie obejdzie, a nieraz czy dwa mój kod nie działał wg. założeń.

struct DRIVER_NIC_I82540EM_STRUCTURE_MMIO *mmio = (struct DRIVER_NIC_I82540EM_STRUCTURE_MMIO *) driver_nic_i82540em_mmio_base_address;
mmio -> eerd = 1;
driver_nic_i82540em_mac[ 0 ] = mmio -> eerd >> 16;

Kompilator wypluje:

screenshot-20211028081833.png

Co było bardzo niemiłe z jego strony ;)

Natomiast gdy dodamy volatile na początku deklarowania wskaźnika.

volatile struct DRIVER_NIC_I82540EM_STRUCTURE_MMIO *mmio = (struct DRIVER_NIC_I82540EM_STRUCTURE_MMIO *) driver_nic_i82540em_mmio_base_address;
mmio -> eerd = 1;
driver_nic_i82540em_mac[ 0 ] = mmio -> eerd >> 16;

Otrzymamy prawidłowe odzworowanie kodu:

screenshot-20211027210418.png

Także... nie ufaj kompilatorowi ;)

8

@teofrast twój problem polega na tym, ze za dużo rozmyślasz "co by tu zrobić" a za mało robisz.

4

Pisząc jakikolwiek program dobrze jest wiedzieć w jaki sposób zostanie on przetłumaczony na bytecode – taka wiedza pomaga w pisaniu wysokowydajnych aplikacji. Zaznaczenie opcji „Optimize Code” w IDE i liczenie na to, że kompilator wygeneruję maksymalnie wydajny kod jest trochę naiwne.

Dzięki znajomości asemblera:
Zrozumiesz, że wywołania funkcji nie są darmowe i dlaczego stos wywołań może się przepełnić (np. w funkcjach rekurencyjnych).
Zrozumiesz, w jaki sposób argumenty są przekazywane do parametrów funkcji i jak można to zrobić (kopiowanie pamięci, wskazywanie na pamięć).
Zrozumiesz, że pamięć nie jest darmowa i jak cenne jest automatyczne zarządzanie pamięcią.
Zrozumiesz, jak działa przepływ kontroli na najbardziej podstawowym poziomie.

0

@ple:

ple napisał(a):

Pisząc jakikolwiek program dobrze jest wiedzieć w jaki sposób zostanie on przetłumaczony na bytecode – taka wiedza pomaga w pisaniu wysokowydajnych aplikacji. Zaznaczenie opcji „Optimize Code” w IDE i liczenie na to, że kompilator wygeneruję maksymalnie wydajny kod jest trochę naiwne.

Dzięki znajomości asemblera:
Zrozumiesz, że wywołania funkcji nie są darmowe

i dlaczego stos wywołań może się przepełnić (np. w funkcjach rekurencyjnych)

Przy okazji znalezione:

0

Dzięki znajomości asemblera:
Zrozumiesz, że wywołania funkcji nie są darmowe i dlaczego stos wywołań może się przepełnić (np. w funkcjach rekurencyjnych).

Ale często wywołania funkcji są inline'owane przez kompilator (tego czy innego języka). Ale tu znowu - trzeba doczytać, co robi kompilator oraz ew. zrozumieć jego output. Myślę więc, że może ważniejszą kwestią przy pisaniu kodu konkretnego języka jest orientacja, jak działa konkretny kompilator/wirtualna maszyna. Szczególnie jeśli co chwila wchodzą nowe optymalizacje (jak np. w V8).

Z drugiej strony asembler pokazuje ciekawe wzorce projektowe - choćby wzorzec stosu, który można zastosować w masie innych sytuacji.

(kopiowanie pamięci, wskazywanie na pamięć).

I koszty, które się z tym wiążą. I dlaczego np. warto się odwoływać do pamięci w sposób uporządkowany, żeby cache złapał, a nie latać po całej pamięci (locality of reference)

1

Nawet instrukcja inkrementacji zmiennej to trzy operacje atomowe w bytecode – ta wiedza ma kluczowe znaczenie przy programowaniu wielowątkowym.

1 użytkowników online, w tym zalogowanych: 0, gości: 1