Szukaj…


Uwagi

Zestaw to ogólna nazwa używana dla wielu czytelnych dla człowieka form kodu maszynowego. Oczywiście różni się znacznie między różnymi procesorami (Central Processing Unit), ale także na pojedynczym CPU może istnieć kilka niekompatybilnych dialektów asemblera, każdy skompilowany przez inny asembler, w identyczny kod maszynowy zdefiniowany przez twórcę CPU.

Jeśli chcesz zadać pytanie dotyczące własnego problemu z montażem, zawsze określ, jakiego sprzętu i jakiego asemblera używasz, w przeciwnym razie trudno będzie szczegółowo odpowiedzieć na twoje pytanie.

Nauka Montaż pojedynczego konkretnego procesora pomoże nauczyć się podstaw różnych procesorów, ale każda architektura HW może mieć znaczne różnice w szczegółach, więc nauka ASM na nowej platformie może być bliska nauki od zera.

Spinki do mankietów:

X86 Assembly Wikibook

Wprowadzenie

Język asemblera to czytelna dla człowieka forma języka maszynowego lub kodu maszynowego, który jest faktyczną sekwencją bitów i bajtów, na których działa logika procesora. Zasadniczo ludziom łatwiej jest czytać i programować w języku mnemonicznym niż binarnym, ósemkowym lub szesnastkowym, więc ludzie zwykle piszą kod w języku asemblera, a następnie używają jednego lub więcej programów do konwersji go na format języka maszynowego rozumiany przez procesor.

PRZYKŁAD:

mov eax, 4
cmp eax, 5
je point

Asembler to program, który czyta program w języku asemblera, analizuje go i tworzy odpowiedni język maszynowy. Ważne jest, aby zrozumieć, że w przeciwieństwie do języka takiego jak C ++, który jest jednym językiem zdefiniowanym w standardowym dokumencie, istnieje wiele różnych języków asemblera. Każda architektura procesora, ARM, MIPS, x86 itp. Ma inny kod maszynowy, a zatem inny język asemblera. Ponadto czasami istnieje wiele różnych języków asemblera dla tej samej architektury procesorów. W szczególności rodzina procesorów x86 ma dwa popularne formaty, które są często nazywane składnią gazu ( gas to nazwa pliku wykonywalnego dla GNU Assembler) i składni Intela (nazwanej od twórcy rodziny procesorów x86). Są różne, ale równoważne, ponieważ zazwyczaj można napisać dowolny program w dowolnej składni.

Ogólnie rzecz biorąc, twórca procesora dokumentuje procesor i jego kod maszynowy oraz tworzy język asemblera. Często używany jest ten konkretny język asemblera, ale w przeciwieństwie do autorów kompilatorów próbujących dostosować się do standardu językowego, asemblerem zdefiniowanym przez wynalazcę procesora jest zazwyczaj, ale nie zawsze, wersja używana przez ludzi piszących asemblery .

Istnieją dwa ogólne typy procesorów:

  • CISC (Complex Instruction Set Computer): posiada wiele różnych i często skomplikowanych instrukcji języka maszynowego

  • RISC (Komputery z ograniczoną liczbą instrukcji): dla kontrastu ma mniej i prostszych instrukcji

W przypadku programisty asemblera różnica polega na tym, że procesor CISC może nauczyć się bardzo wielu instrukcji, ale często istnieją instrukcje dostosowane do określonego zadania, podczas gdy procesory RISC mają mniej i prostszych instrukcji, ale każda operacja może wymagać programowania w asemblerze napisać więcej instrukcji, aby zrobić to samo.

Kompilatory innych języków programowania czasami najpierw wytwarzają asembler, który następnie jest kompilowany do kodu maszynowego przez wywołanie asemblera. Na przykład gcc za pomocą własnego asemblera gazu w końcowym etapie kompilacji. Wytworzony kod maszynowy jest często przechowywany w plikach obiektowych , które można połączyć w plik wykonywalny przez program łączący.

Kompletny „łańcuch narzędzi” często składa się z kompilatora, asemblera i linkera. Następnie można użyć tego asemblera i linkera bezpośrednio do pisania programów w języku asemblera. W świecie GNU pakiet binutils zawiera asembler i linker oraz powiązane narzędzia; ci, którzy są wyłącznie zainteresowani programowaniem w asemblerze, nie potrzebują gcc ani innych pakietów kompilatora.

Małe mikrokontrolery są często programowane wyłącznie w języku asemblera lub w kombinacji języka asemblera i jednego lub więcej języków wyższego poziomu, takich jak C lub C ++. Odbywa się to, ponieważ często można wykorzystać szczególne aspekty architektury zestawu instrukcji dla takich urządzeń do napisania bardziej zwartego, wydajnego kodu, niż byłoby to możliwe w języku wyższego poziomu, a takie urządzenia często mają ograniczoną pamięć i rejestry. Wiele mikroprocesorów jest używanych w systemach wbudowanych, które są urządzeniami innymi niż komputery ogólnego przeznaczenia, które akurat zawierają mikroprocesor. Przykładami takich systemów wbudowanych są telewizory, kuchenki mikrofalowe i jednostka sterująca silnika nowoczesnego samochodu. Wiele takich urządzeń nie ma klawiatury ani ekranu, więc programista zazwyczaj pisze program na komputerze ogólnego przeznaczenia, uruchamia cross-asembler (tzw. Ponieważ ten rodzaj asemblera wytwarza kod dla innego rodzaju procesora niż ten, na którym działa ) i / lub kompilator krzyżowy i linker do tworzenia kodu maszynowego.

Istnieje wielu dostawców takich narzędzi, które są tak różnorodne, jak procesory, dla których produkują kod. Wiele, ale nie wszystkie procesory mają również rozwiązania typu open source, takie jak GNU, sdcc, llvm lub inne.

Kod maszynowy

Kod maszynowy to termin odnoszący się do danych w konkretnym rodzimym formacie maszynowym, które są przetwarzane bezpośrednio przez maszynę - zwykle przez procesor zwany CPU (Central Processing Unit).

Wspólna architektura komputera (architektura von Neumanna ) składa się z procesora ogólnego przeznaczenia (CPU), pamięci ogólnego przeznaczenia - przechowującej zarówno program (ROM / RAM), jak i przetwarzane dane oraz urządzenia wejściowe i wyjściowe (urządzenia I / O).

Główną zaletą tej architektury jest względna prostota i uniwersalność każdego z komponentów - w porównaniu do wcześniejszych komputerów (z programami wbudowanymi w konstrukcję maszyny) lub architektur konkurencyjnych (na przykład architektura Harvarda oddzielająca pamięć programu od pamięci dane). Wadą jest nieco gorsza ogólna wydajność. W dłuższej perspektywie uniwersalność pozwoliła na elastyczne użycie, które zwykle przewyższało koszt wydajności.

Jak to się ma do kodu maszynowego?

Program i dane są przechowywane na tych komputerach jako liczby w pamięci. Nie ma prawdziwego sposobu na odróżnienie kodu od danych, więc systemy operacyjne i operatorzy maszyn podają wskazówki procesora, w którym punkcie wejścia pamięci uruchamia program, po załadowaniu wszystkich liczb do pamięci. CPU odczytuje następnie instrukcję (liczbę) zapisaną w punkcie wejścia i przetwarza ją rygorystycznie, kolejno odczytując kolejne liczby jako dalsze instrukcje, chyba że sam program nakazuje CPU kontynuowanie wykonywania w innym miejscu.

Na przykład dwie 8-bitowe liczby (8 bitów zgrupowanych razem jest równych 1 bajtowi, to jest liczba całkowita bez znaku w zakresie 0-255): 60 201 , gdy zostanie wykonana jako kod na Zilog Z80 CPU, będzie przetwarzana jako dwie instrukcje: INC a (zwiększając wartość w rejestrze przez jeden) i a RET (powrót z sub-rutyny, wskazując CPU wykonać instrukcje od innej części pamięci).

Aby zdefiniować ten program, człowiek może wprowadzić te liczby za pomocą edytora pamięci / pliku, na przykład w edytorze szesnastkowym jako dwa bajty: 3C C9 (liczby dziesiętne 60 i 201 zapisane w kodowaniu podstawowym 16). To byłoby programowanie w kodzie maszynowym .

Aby ułatwić ludziom programowanie procesora, stworzono programy asemblera , zdolne do odczytu pliku tekstowego zawierającego coś takiego:

subroutineIncrementA:
    INC   a
    RET

dataValueDefinedInAssemblerSource:
    DB    60          ; define byte with value 60 right after the ret

generowanie bajtów sekwencji liczb szesnastkowych 3C C9 3C , owiniętych opcjonalnymi dodatkowymi liczbami specyficznymi dla platformy docelowej: zaznaczenie, która część takiego pliku binarnego jest kodem wykonywalnym, gdzie jest punkt wejścia programu (pierwsza jego instrukcja), które części są zakodowane dane (niewykonywalne) itp.

Zauważ, że programista określił ostatni bajt o wartości 60 jako „dane”, ale z perspektywy CPU nie różni się w żaden sposób od bajtu INC a . Do programu wykonawczego należy poprawne nawigowanie procesora po bajtach przygotowanych jako instrukcje i przetwarzanie bajtów danych tylko jako danych instrukcji.

Takie dane wyjściowe są zwykle przechowywane w pliku na urządzeniu pamięci masowej, ładowanym później przez system operacyjny ( system operacyjny - kod maszynowy już uruchomiony na komputerze, pomagający w manipulowaniu komputerem ) do pamięci przed jego wykonaniem i wreszcie skierowaniem procesora na punkt wejścia programu.

Procesor może przetwarzać i wykonywać tylko kod maszynowy - ale dowolna zawartość pamięci, nawet losowa, może być przetwarzana jako taka, chociaż wynik może być losowy, od „ awarii ” wykrytej i obsługiwanej przez system operacyjny do przypadkowego wyczyszczenia danych z I / O urządzenia lub uszkodzenie wrażliwych urządzeń podłączonych do komputera (niezbyt częste przypadki komputerów domowych :)).

Podobnym procesem towarzyszy wiele innych języków programowania wysokiego poziomu, kompilujących źródło (program w postaci tekstu czytelnego dla człowieka) na liczby, albo reprezentujące kod maszynowy (natywne instrukcje CPU), albo w przypadku języków interpretowanych / hybrydowych do niektórych ogólnych specyficzny dla języka kod maszyny wirtualnej, który jest następnie dekodowany na natywny kod maszynowy podczas wykonywania przez interpretera lub maszynę wirtualną.

Niektóre kompilatory używają Asemblera jako pośredniego etapu kompilacji, najpierw tłumacząc źródło na formę Asemblera, a następnie uruchamiając narzędzie asemblera, aby uzyskać z niego końcowy kod maszynowy (przykład GCC: uruchom gcc -S helloworld.c aby uzyskać wersję asemblera programu C. helloworld.c ).

Hello world dla systemu Linux x86_64 (Intel 64-bitowy)

section .data
    msg db "Hello world!",10      ; 10 is the ASCII code for a new line (LF)

section .text
    global _start

_start:
    mov rax, 1
    mov rdi, 1
    mov rsi, msg
    mov rdx, 13
    syscall
    
    mov rax, 60
    mov rdi, 0
    syscall

Jeśli chcesz uruchomić ten program, najpierw potrzebujesz Netwide Assembler , nasm , ponieważ ten kod używa swojej składni. Następnie użyj następujących poleceń (zakładając, że kod znajduje się w pliku helloworld.asm ). Są one potrzebne odpowiednio do montażu, łączenia i wykonywania.

  • nasm -felf64 helloworld.asm
  • ld helloworld.o -o helloworld
  • ./helloworld

Kod korzysta z funkcji sys_write w systemie Linux. Tutaj możesz zobaczyć listę wszystkich wywołań systemowych dla architektury x86_64. Kiedy weźmiesz również pod uwagę strony podręcznika zapisu i wyjścia , możesz przetłumaczyć powyższy program na C, który robi to samo i jest znacznie bardziej czytelny:

#include <unistd.h>

#define STDOUT 1

int main()
{
    write(STDOUT, "Hello world!\n", 13);
    _exit(0);
}

Potrzebne są tutaj tylko dwa polecenia do kompilacji i łączenia (pierwsze) i wykonywania:

  • gcc helloworld_c.c -o helloworld_c .
  • ./helloworld_c

Hello World dla OS X (x86_64, gaz składniowy Intela)

.intel_syntax noprefix

.data

.align 16
hello_msg:
    .asciz "Hello, World!"

.text

.global _main
_main:
    push rbp
    mov rbp, rsp

    lea rdi, [rip+hello_msg]
    call _puts

    xor rax, rax
    leave
    ret

Montować:

clang main.s -o hello
./hello

Uwagi:

Wykonywanie zestawu x86 w Visual Studio 2015

Krok 1 : Utwórz pusty projekt za pomocą Plik -> Nowy projekt .

wprowadź opis zdjęcia tutaj

Krok 2 : Kliknij prawym przyciskiem myszy rozwiązanie projektu i wybierz Zbuduj zależności-> Zbuduj dostosowania .

Krok 3 : Zaznacz pole wyboru „.masm” .

wprowadź opis zdjęcia tutaj

Krok 4 : Naciśnij przycisk „ok” .

Krok 5 : Utwórz plik zestawu i wpisz:

.386
    .model small
        .code

            public main
                main proc

                    ; Ends program normally

                    ret
                main endp
            end main

Krok 6 : Kompiluj!

wprowadź opis zdjęcia tutaj



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow