Najbardziej podstawowy Assembler

1

Dzień dobry,
Będę bardzo wdzięczny, jeżeli ktoś zna kurs assemblera pod x86, taki bez implementacji absolutnie żadnych procedur (bez LOOP, bez stosu itp.)
Tylko rejestry, tylko te instrukcje, co mają swój sprzętowy odpowiednik na procesorze, byłoby super, gdyby były tam wyjaśnione przerwania(ale nie te systemowe) i w jaki sposób komunikować się ze sprzętem( np. klawiatura, monitor, a przypomnę, że "OUT" również odpada, ponieważ nie chcę żadnych procedur)

Bardzo interesuje mnie jak komputer działa na najniższym poziomie, chciałbym spróbować coś sklecić kompletnie samemu, szczerze mówiąc najchętniej sięgnąłbym na ten moment po opcodes i napisał własny kompilator wraz z językiem.. może kiedyś("kiedyś" to może własny procesorek skonstruuję);)

Będę bardzo wdzięczny za każdą wskazówkę, gdzie szukać takich informacji.

3
  1. IN i OUT to nie procedury, a instrukcje procesora na najniższym poziomie: https://en.wikibooks.org/wiki/X86_Assembly/Other_Instructions#I.2FO_Instructions
  1. Polecam https://flatassembler.net/ bo tam dużo rzeczy (ułatwiaczy) robione jest za pomocą makr i można je bez problemu przejrzeć (są normalnym elementem kodu źródłowego) - widać konkretnie do jakich instrukcji procesora się rozwijają.
  2. W FASMie napisane są systemy MenuetOS czy jego fork KolibriOS. Zapewne będzie w miarę łatwo je przejrzeć (są "trochę" mniejsze niż Linux) i zrozumieć jak system gada ze sprzętem.
  3. Jeśli będziesz mniej więcej być w stanie przewidzieć do czego skompiluje się kod w C to nie będzie już dużego sensu pisać w czystym asemblerze. Kompilatory C/ C++ mają opcję wypluwania kodu asemblerowego i wrzucania do komentarzy kawałków kod C/ C++ z których powstaje kod asemblerowy pod komentarzem. Czytając oba kody i je porównując nabierzesz intuicji na temat tego co się dzieje w kodzie C i kodzie asemblerowym.
  4. W takim np Linuksie kodu asemblerowego jest tyle co kot napłakał, a mimo to wszystko świetnie hula. To dlatego, że kodu wymagającego asemblera jest bardzo mało i przez >99% czasu pisząc w asemblerze po prostu piszesz w bardziej rozwlekłym języku i nie dostajesz nic w zamian (oprócz wykonania optymalizacji z użyciem specjalistycznych instrukcji z wykorzystaniem których słabo sobie radzi kompilator).
0

Dzięki wielkie za informację o IN i OUT, mój błąd;D
Flatassembler też super!!!
Na ten moment, mam to czego potrzebowałem, wielkie dzięki

0

Będę bardzo wdzięczny, jeżeli ktoś zna kurs assemblera pod x86, taki bez implementacji absolutnie żadnych procedur (bez LOOP, bez stosu itp.)
Tylko rejestry, tylko te instrukcje, co mają swój sprzętowy odpowiednik na procesorze, byłoby super, gdyby były tam wyjaśnione przerwania(ale nie te systemowe) i w jaki sposób komunikować się ze sprzętem( np. klawiatura, monitor, a przypomnę, że "OUT" również odpada, ponieważ nie chcę żadnych procedur)

Co to znaczy bez LOOP bez stosu i bez OUT?
LOOP i OUT to właśnie instrukcje procesora, a stos to po prostu obszar pamięci, obsługiwany m.in. instrukcjami PUSH i POP.

0

Z tego co wiem to np. entry to push esp, mov esp, ebp.
A np. leave to mov esp, ebp, pop esp.
A z kolei call xxx to push $+5, jmp xxx

lea też ma jakiś trick, z kolei to jest nawet ciekawe.
Ale samemu można odkryć koło na nowo, bo tak akurat się składa, że jak się jest początkującym.
To można nie wiedzieć, że coś już odkryto i wpaść na to samemu.

0

Ale że co niby odkryto? x86 już dawno jest kompletny w sensie maszyny Turinga, a instrukcji jest tam tyle, że bez problemu da się emulować jedne drugimi - no chyba, że chodzi o jakieś rozszerzenia bezpieczeństwa czy komunikacji z peryferiami. Intel dokłada sobie ciągle nowe instrukcje tak jak mu wygodnie, czasami nawet bardzo zakręcone jak PCMPISTRI/ PCMPISTRM/ PCMPESTRI/ PCMPESTRM: http://gynvael.coldwind.pl/?id=386

Na samym początku istnienia x86 programowanie w czystym asemblerze było normą, więc Intel nawrzucał do x86 kupę instrukcji, które uprzyjemniały to pisanie w czystym asemblerze. Później jednak programowanie w czystym asemblerze stało się niszowym zajęciem i znikła potrzeba posiadania ładnych i czytelnych instrukcji. Intel pracując nad kolejnymi wersjami swoich procesorów musiał rozsądnie przeznaczać zasoby na optymalizację konkretnych instrukcji - push, pop i mov występowały znacznie częściej niż enter i leave i miały znacznie większy wpływ na wydajność, więc Intel optymalizował szybkość wykonania pierwszej trójki. Autorzy kompilatorów języków wysokopoziomowych to zauważyli i kompilatory zaczęły wypluwać np parę push + mov zamiast enter. Po pewnym czasie enter stało się tak rzadko wykorzystywane, że Intelowi nawet nie chciało się optymalizować jego implementacji w swoich procesorach. Stąd taki enter stał się znacznie wolniejszy od kombinacji push + mov.

0
Biały Pomidor napisał(a):

Z tego co wiem to np. entry to push esp, mov esp, ebp.
A np. leave to mov esp, ebp, pop esp.
A z kolei call xxx to push $+5, jmp xxx

call ma takie działanie jak push i jmp, ale to mimo wszystko jest osobna instrukcja, nie coś co ci asembler zamienia.
call to call, a push+jmp to push+jmp.

tak samo jest z entry i leave.

0

Po pewnym czasie enter stało się tak rzadko wykorzystywane, że Intelowi nawet nie chciało się optymalizować jego implementacji w swoich procesorach. Stąd taki enter stał się znacznie wolniejszy od kombinacji push + mov.

Postanowiłem sprawdzić, jak to jest z tą mityczną powolnością enter:

[bits 32]

segment .text

global _enter_leave
_enter_leave:
	enter 0, 0
	nop
	leave
ret

global _push_mov
_push_mov:
	push ebp
	mov  ebp, esp
	nop
	mov  esp, ebp
	pop  ebp
ret

global _enter_leave_20
_enter_leave_20:
	enter 20, 0
	nop
	leave
ret

global _push_mov_20
_push_mov_20:
	push ebp
	mov  ebp, esp
	sub esp, 20
	nop
	mov  esp, ebp
	pop  ebp
ret
#include <time.h>
#include <stdio.h>

extern void enter_leave();
extern void push_mov();
extern void enter_leave_20();
extern void push_mov_20();

int main()
{
	const int count = 1000000000;
	
	clock_t t0 = clock();
	
	for (int i=0; i<count; i++)
		enter_leave();
		
	clock_t t1 = clock();
	
	for (int i=0; i<count; i++)
		push_mov();
		
	clock_t t2 = clock();
		
	printf("enter/leave\t%f s\n", (float)(t1-t0)/CLOCKS_PER_SEC);
	printf("push/mov\t%f s\n\n", (float)(t2-t1)/CLOCKS_PER_SEC);

	t0 = clock();
	
	for (int i=0; i<count; i++)
		enter_leave_20();	
		
	t1 = clock();
	
	for (int i=0; i<count; i++)
		push_mov_20();
		
	t2 = clock();
	
	printf("enter n/leave\t%f s\n", (float)(t1-t0)/CLOCKS_PER_SEC);
	printf("push/mov/sub\t%f s\n", (float)(t2-t1)/CLOCKS_PER_SEC);
}

Atom N570 (1,66 GHz):

enter/leave     24.078000 s
push/mov        7.984000 s
enter n/leave   23.218000 s
push/mov/sub    7.985000 s

Core 2 Quad Q6600 (2,4 GHz)

enter/leave     5.031000 s
push/mov        4.547000 s
enter n/leave   5.031000 s
push/mov/sub    3.484000 s

Interesujące. Różnica bywa... różna. Ale to i tak w hello worldach bez znaczenia.

0

Postanowiłem sprawdzić, jak to jest z tą mityczną powolnością enter

No i jak? "Mit" obalony?

Zamiast samemu próbować wyliczać czasy pojedynczych instrukcji lepiej jest zajrzeć do manuali od gości, którzy zajmują się takim liczeniem profesjonalnie. Ja polecam: http://agner.org/optimize/ Dla większości instrukcji jest podane opóźnienie, przepustowość i rozbicie na porty wykonawcze. Tablice są dla wielu generacji procesorów.

Dla Atoma 330:

Instrukcja | Mikrooperacje | Opóźnienie | Przepustowość (odwrotność)
--- | --- | ---
mov r,r | 1 | 1 | 1/2
push r | 1 | 1 | 1
enter a,0 | 14 | 23 | ???
enter a,b | 20+6b | ??? | ???

Dla Core 2 / Merom / T5500:

Instrukcja | Mikrooperacje (fused domain) | Opóźnienie | Przepustowość (odwrotność)
--- | --- | ---
mov r,r | 1 | 1 | 1/3
push r | 1 | 3 | 1
enter a,0 | 12 | ??? | 8

Dla Skylake:

Instrukcja | Mikrooperacje (fused domain) | Opóźnienie | Przepustowość (odwrotność)
--- | --- | ---
mov r,r | 1 | 0-1 | 1/4
push r | 1 | 3 | 1
enter a,0 | 12 | ??? | 8
enter a,b | ~14+7b | ~87+2b | ???

Mityczne czy nie mityczne? Oto jest pytanie!

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