En liten geting i c och assembler. Assembler School: Operativsystemutveckling


Original: AsmSchool: Gör ett operativsystem
Författare: Mike Saunders
Publiceringsdatum: 15 april 2016
Översättning: A. Panin
Datum för överföring: 16 april 2016

Del 4: Med de färdigheter du har lärt dig från tidigare artiklar i serien kan du börja utveckla ditt eget operativsystem!

Vad är det för?

  • För att förstå hur kompilatorer fungerar.
  • För att förstå instruktionerna för CPU.
  • För att optimera din kod för prestanda.

Under loppet av flera månader har vi passerat en svår väg, som började med utvecklingen av enkla program i assemblerspråk för Linux och slutade i den sista artikeln i serien med utvecklingen av självförsörjande kod som körs på en persondator utan operativsystem. Nåväl, nu ska vi försöka samla all information tillsammans och skapa ett riktigt operativsystem. Ja, vi kommer att gå i Linus Torvalds fotspår, men först är det värt att svara på följande frågor: "Vad är ett operativsystem? Vilka av dess funktioner måste vi återskapa?"

I den här artikeln kommer vi bara att fokusera på operativsystemets huvudfunktioner: ladda och köra program. Komplexa operativsystem utför många fler funktioner, som att hantera virtuellt minne och bearbeta nätverkspaket, men de kräver år av kontinuerlig drift för att implementera dem korrekt, så i den här artikeln kommer vi bara att täcka de grundläggande funktionerna som finns i alla operativsystem. Förra månaden utvecklade vi ett litet program som passade i en 512-byte sektor på en diskett (dess första sektor), och nu kommer vi att ändra det något för att lägga till funktionen att ladda ytterligare data från disken.

Utveckling av startladdare

Vi skulle kunna försöka minska storleken på den binära koden för vårt operativsystem så mycket som möjligt för att placera den i den första 512-bytesektorn på en diskett, samma som laddas av BIOS, men i det här fallet kommer inte att kunna implementera några intressanta funktioner. Därför kommer vi att använda dessa 512 byte för att hysa den binära koden för en enkel starthanterare som kommer att ladda den binära koden för OS-kärnan i RAM-minnet och exekvera den. (Efter det kommer vi att utveckla själva OS-kärnan, som kommer att ladda den binära koden för andra program från disken och även köra den, men detta kommer att diskuteras lite senare.)

Du kan ladda ner källkoden för exemplen som diskuteras i den här artikeln på www.linuxvoice.com/code/lv015/asmschool.zip. Och det här är vår bootloader-kod från en fil som heter boot.asm:

BITS 16 jmp kort start; Hoppa till etikett skipping disk description nop; Tillägg före diskbeskrivning % inkluderar "bpb.asm" start: mov ax, 07C0h; Ladda adress mov ds, ax; Datasegment mov ax, 9000h; Förbered stacken mov ss, axe mov sp, 0FFFFh; Högen växer ner! cld; Ställa in riktningsflaggan mov si, kern_filename anrop load_file jmp 2000h: 0000h; Hoppa till OS-kärnan binär kod laddad från filen kern_filename db "MYKERNELBIN"% inkluderar "disk.asm" gånger 510 - ($ - $$) db 0; Komplettering av den binära koden med nollor upp till 510 byte dw 0AA55h; Bootloader binär kod slutetikett buffert:; Start av buffert för diskinnehåll

I den här koden är den första CPU-instruktionen jmp-instruktionen, som finns efter BITS-direktivet som säger till NASM-montören att använda 16-bitarsläge. Som du säkert minns från den tidigare artikeln i serien börjar exekveringen av en 512-byte binär kod som laddas med hjälp av BIOS från disken från första början, men vi måste gå till etiketten för att hoppa över en speciell uppsättning data. Förra månaden skrev vi uppenbarligen koden till början av disken (med hjälp av verktyget dd) och lämnade resten av diskutrymmet tomt.

Nu måste vi använda en diskett med ett lämpligt MS-DOS-filsystem (FAT12), och för att fungera korrekt med detta filsystem måste vi lägga till en uppsättning specialdata nära början av sektorn. Denna uppsättning kallas BIOS Parameter Block (BPB) och innehåller information som disketikett, antal sektorer och så vidare. Det borde inte intressera oss i detta skede, eftersom mer än en serie artiklar kan ägnas åt sådana ämnen, varför vi har placerat alla relaterade instruktioner och data i en separat källkodsfil som heter bpb.asm.

Baserat på ovanstående är detta direktiv från vår kod extremt viktigt:

% inkluderar "bpb.asm"

Detta är ett NASM-direktiv som tillåter att innehållet i en specificerad källfil inkluderas i den aktuella källfilen under montering. Således kan vi göra koden för vår bootloader så kort och begriplig som möjligt, och föra alla detaljer om implementeringen av BIOS-parameterblocket i en separat fil. BIOS-parameterblocket ska placeras tre byte efter början av sektorn, och eftersom jmp-instruktionen bara tar två byte, måste vi använda nop-instruktionen (dess namn står för "no operation" - det här är en instruktion som inte gör någonting förutom slösa CPU-cykler ) för att fylla i den återstående byten.

Jobbar med stacken

Därefter måste vi använda instruktioner som liknar de som diskuterades i den förra artikeln för att förbereda registren och stacken, såväl som cld-instruktionen (står för "clear direction"), som låter dig ställa in riktningsflaggan för vissa instruktioner , såsom lodsb-instruktionen, som efter dess utförande kommer att öka värdet i SI-registret, inte minska det.

Efter det lägger vi adressen till strängen i SI-registret och anropar vår load_file-funktion. Men tänk efter en minut – vi har inte utvecklat den här funktionen än! Ja, det är sant, men dess implementering kan hittas i en annan källfil som vi inkluderar, kallad disk.asm.

FAT12-filsystemet som används på disketter som är formaterade i MS-DOS är ett av de enklaste filsystemen som finns, men det kräver också mycket kod för att fungera med dess innehåll. Subrutinen load_file är cirka 200 rader lång och kommer inte att presenteras i den här artikeln, eftersom vi överväger processen att utveckla ett operativsystem och inte en drivrutin för ett specifikt filsystem, därför är det inte särskilt rimligt att slösa utrymme på loggsidorna på detta sätt. I allmänhet inkluderade vi källfilen disk.asm nästan innan slutet av den aktuella källfilen och kan glömma det. (Om du fortfarande är intresserad av strukturen för FAT12-filsystemet kan du kolla in den utmärkta översikten på http://tinyurl.com/fat12spec, ta sedan en titt på disk.asm-källfilen - koden som finns i den är bra kommenterat.)

Hur som helst laddar subrutinen load_file den binära koden från filen med det namn som anges i SI-registret till segment 2000 med en offset på 0, varefter vi hoppar till dess början för exekvering. Och det är allt - operativsystemets kärna är laddad och starthanteraren har gjort sitt jobb!

Du kanske har märkt att vår kod använder MYKERNELBIN som namn på operativsystemets kärnfil istället för MYKERNEL.BIN, vilket passar bra med namnschemat 8 + 3 som används på DOS-disketter. Faktum är att FAT12-filsystemet använder en intern representation av filnamn, och vi sparar utrymme genom att använda ett filnamn som garanterat inte kräver implementering av en mekanism inom vår load_file-subrutin för att hitta punkttecknet och konvertera filnamnet till den interna filsystemets representation.

Efter raden med direktivet att ansluta källkodsfilen disk.asm, finns det två rader avsedda att fylla den binära koden för starthanteraren med nollor till 512 byte och inkludera slutmärket för dess binära kod (detta diskuterades i sista artikeln). Slutligen, i slutet av koden finns "buffert"-etiketten, som används av subrutinen load_file. I allmänhet kräver subrutinen load_file ledigt utrymme i RAM-minnet för att utföra några mellansteg i processen att söka efter en fil på disken, och vi har tillräckligt med ledigt utrymme efter att ha laddat starthanteraren, så vi placerar bufferten här.

För att montera starthanteraren, använd följande kommando:

Nasm -f bin -o boot.bin boot.asm

Nu måste vi skapa en virtuell diskettavbildning i MS-DOS-format och lägga till den binära koden för vår bootloader till dess första 512 byte med hjälp av följande kommandon:

Mkdosfs -C floppy.img 1440 dd conv = notrunc if = boot.bin of = floppy.img

Detta avslutar starthanterarens utvecklingsprocess! Vi har nu en startdiskettavbildning som låter oss ladda operativsystemets kärnbinär från en fil som heter mykernel.bin och köra den. Vidare väntar en mer intressant del av arbetet på oss - utvecklingen av själva operativsystemets kärna

Operativsystems kärna

Vi vill att kärnan i vårt operativsystem ska utföra många viktiga uppgifter: visa en hälsning, acceptera input från användaren, avgöra om inmatningen är ett kommando som stöds och köra program från disken efter att användaren har angett sina namn. Det här är operativsystemets kärnkod från filen mykernel.asm:

Mov ax, 2000h mov ds, ax mov es, ax loop: mov si, prompt call lib_print_string mov si, user_input call lib_input_string cmp byte, 0 je loop cmp word, "ls" je list_files mov ax, si mov cx, 327_68 call lib jc load_fail anrop 32768 jmp loop load_fail: mov si, load_fail_msg anrop lib_print_string jmp loop list_files: mov si, file_list anrop lib_get_file_list anrop lib_print_string jmp loop prompt db 13, 10, "MyOS_no, _0, "MyOS_fail>, _0, "MyOS_fail>, _0 user_input gånger 256 db 0 file_list gånger 1024 db 0% inkluderar" lib.asm "

Innan du tittar på koden bör du vara uppmärksam på den sista raden med direktivet för att inkludera källkodsfilen lib.asm, som också finns i asmschool.zip-arkivet från vår webbplats. Detta är ett bibliotek med användbara rutiner för att arbeta med skärm, tangentbord, strängar och diskar, som du också kan använda - i det här fallet inkluderar vi denna källkodsfil i slutet av huvudkällfilen för operativsystemets kärna i för att göra den senare så kompakt och vacker som möjligt ... Se avsnittet "Lib.asm-rutiner" för mer information om alla tillgängliga rutiner.

I de första tre raderna i operativsystemets kärnkod fyller vi segmentregistren med data för att peka på segment 2000, i vilket den binära koden laddades. Detta är viktigt för att säkerställa att instruktioner som lodsb fungerar korrekt, som ska läsa data från det aktuella segmentet och inte från något annat segment. Därefter kommer vi inte att utföra några ytterligare operationer med segment; vårt operativsystem kommer att köras med 64 KB RAM!

Längre in i koden finns en etikett som motsvarar början av slingan. Först och främst använder vi en av lib.asm biblioteksrutiner, lib_print_string, för att skriva ut hälsningen. Byte 13 och 10 före hälsningsraden är nyradstecken, på grund av vilka hälsningen inte kommer att visas direkt efter utmatningen av något program, utan alltid på en ny rad.

Vi använder sedan en annan lib.asm-subrutin som heter lib_input_string, som tar användarens inskrivna tecken och lagrar dem i en buffert som pekas på i SI-registret. I vårt fall deklareras bufferten nära slutet av operativsystemets kärnkod enligt följande:

User_input gånger 256 db 0

Denna deklaration låter dig skapa en buffert på 256 tecken fylld med nollor — den måste vara tillräckligt stor för att lagra kommandon för ett enkelt operativsystem som vårt!

Därefter validerar vi användarinmatning. Om den första byten i user_input-bufferten är noll, tryckte användaren helt enkelt på Enter utan att ange något kommando; kom ihåg att alla strängar är nollterminerade. Så i det här fallet ska vi bara gå till början av slingan och skriva ut hälsningen igen. Men i händelse av att användaren anger något kommando måste vi först kontrollera om han har skrivit in kommandot ls. Hittills har du bara kunnat observera jämförelser av enskilda bytes i våra assemblerspråksprogram, men glöm inte att det också är möjligt att utföra jämförelser av tvåbytevärden eller maskinord. I denna kod jämför vi det första maskinordet från user_input-bufferten med maskinordet som motsvarar raden ls och, om de är identiska, går vi vidare till kodblocket nedan. Inom detta kodblock använder vi en annan subrutin från lib.asm för att få en kommaseparerad lista med filer på disken (som måste lagras i file_list-bufferten), visa den listan och gå tillbaka till loopen för att bearbeta användarinmatning .

Utförande av tredjepartsprogram

Om användaren inte anger kommandot ls antar vi att de skrev in namnet på programmet från disken, så det är vettigt att försöka ladda det. Vårt lib.asm-bibliotek innehåller en implementering av en användbar subrutin lib_load_file, som analyserar tabellerna i FAT12-filsystemet på en disk: den tar en pekare till början av en rad med ett filnamn med hjälp av AX-registret, samt en offsetvärde för att ladda den binära koden från programfilen med hjälp av CX-registret. Vi använder redan SI-registret för att lagra en pekare till användarinmatningssträngen, så vi kopierar den pekaren till AX-registret och lägger sedan in värdet 32768, som används som en offset för att ladda binären från programfilen, i CX-registret .

Men varför använder vi just detta värde som en offset för att ladda den binära koden från programfilen? Tja, detta är bara ett av minneskartalternativen för vårt operativsystem. På grund av det faktum att vi arbetar i ett 64 KB-segment och den binära koden för vår kärna laddas med offset 0, måste vi använda de första 32 KB minne för kärndata och de återstående 32 KB för data av de laddade programmen. Offset 32768 är alltså mitten av vårt segment och tillåter oss att tillhandahålla tillräckligt med RAM-minne för både operativsystemets kärna och de laddade programmen.

Efter det utför subrutinen lib_load_file en extremt viktig operation: om den inte kan hitta en fil med det angivna namnet på disken, eller av någon anledning inte kan läsa den från disken, avslutas den helt enkelt och ställer in en speciell bärflagga. Detta är en flagga för den centrala processorns tillstånd, som är inställd på att utföra vissa matematiska operationer och för tillfället inte borde intressera oss, men samtidigt kan vi bestämma närvaron av denna flagga för att fatta snabba beslut. Om lib_load_asm ställer in bärflaggan använder vi jc-instruktionen (hopp om bär) för att hoppa till kodblocket som matar ut felmeddelandet och återgår till början av användarinmatningsslingan.

I samma fall, om bärflaggan inte är inställd, kan vi dra slutsatsen att subrutinen lib_load_asm framgångsrikt har laddat den binära koden från programfilen till RAM-minnet vid 32768. Allt vi behöver i detta fall är att initiera exekveringen av den binära koden laddas på denna adress , det vill säga starta körningen av programmet som specificerats av användaren! Och efter att det här programmet använder ret-instruktionen (för att återgå till anropskoden), behöver vi bara återgå till användarinmatningsbearbetningsslingan. Således har vi skapat ett operativsystem: det består av de enklaste mekanismerna för att analysera kommandon och ladda program, implementerade i cirka 40 rader assemblerkod, om än med mycket hjälp från subrutinerna från biblioteket lib.asm.

För att sätta ihop operativsystemets kärnkod, använd följande kommando:

Nasm -f bin -o mykernel.bin mykernel.asm

Efter det måste vi på något sätt lägga till filen mykernel.bin till diskettavbildningsfilen. Om du är bekant med att montera skivbilder med loopback-enheter kan du komma åt innehållet i diskavbildningen floppy.img med den, men det finns ett enklare sätt, som är att använda GNU Mtools verktygslåda (www.gnu.org/software) / mtools). Detta är en uppsättning program för att arbeta med disketter som använder MS-DOS / FAT12 filsystem, tillgängliga från programvarupaketen för alla populära Linux-distributioner, så du behöver bara använda apt-get, yum, pacman eller något annat verktyg används för att installera mjukvarupaket i din distribution.

Efter att du har installerat lämpligt programpaket måste du köra följande kommando för att lägga till filen mykernel.bin till diskavbildningsfilen floppy.img:

Mcopy -i floppy.img mykernel.bin :: /

Lägg märke till de roliga karaktärerna i slutet av kommandot: kolon, kolon och snedstreck. Vi är nästan redo att lansera vårt operativsystem nu, men vad är poängen när det inte finns några applikationer för det ännu? Låt oss åtgärda detta missförstånd genom att utveckla en extremt enkel applikation. Ja, nu kommer du att utveckla en applikation för ditt eget operativsystem - tänk bara hur mycket din auktoritet kommer att stiga i nördarnas led. Spara följande kod i en fil som heter test.asm:

Org 32768 mov ah, 0Eh mov al, "X" int 10h ret

Den här koden använder helt enkelt BIOS-funktionen för att visa "X"-tecknet på skärmen och återställer sedan kontrollen till koden som kallade den - i vårt fall är den här koden operativsystemets kod. Organisationsraden, med vilken applikationens källkod börjar, är inte en instruktion från den centrala processorn, utan ett direktiv från NASM assembler som säger att den binära koden kommer att laddas in i RAM vid offset 32768, därför är det nödvändigt att räkna om alla kompensationer med hänsyn till denna omständighet.

Den här koden måste också monteras, och den resulterande binära filen måste läggas till i diskettavbildsfilen:

Nasm -f bin -o test.bin test.asm mcopy -i floppy.img test.bin :: /

Ta nu ett djupt andetag, gör dig redo att begrunda de oöverträffade resultaten av ditt eget arbete och ladda in diskettbilden med en PC-emulator som Qemu eller VirtualBox. Till exempel kan följande kommando användas för detta ändamål:

Qemu-system-i386 -fda floppy.img

Voila: boot.img-starthanteraren, som vi integrerade i den första sektorn av diskavbildningen, laddar operativsystemets kärna mykernel.bin, som visar ett välkomstmeddelande. Ange kommandot ls för att få namnen på två filer som finns på disken (mykernel.bin och test.bin), och ange sedan namnet på den sista filen för att köra den och visa X-tecknet på skärmen.

Det är coolt, eller hur? Nu kan du börja finjustera ditt operativsystems skal, lägga till nya kommandoimplementationer och lägga till ytterligare programfiler på disken. Om du vill köra det här operativsystemet på en riktig PC bör du hänvisa till avsnittet "Köra startladdaren på en riktig hårdvaruplattform" från föregående artikel i den här serien - du behöver exakt samma kommandon. Nästa månad kommer vi att göra vårt operativsystem kraftfullare genom att tillåta nedladdningsbara program att använda systemfunktioner och på så sätt implementera konceptet med koddelning för att minska dubbelarbete. Det mesta av arbetet ligger kvar.

Lib.asm biblioteksrutiner

Som nämnts tidigare, tillhandahåller lib.asm en stor uppsättning användbara rutiner för användning inom ditt operativsystems kärnor och individuella program. Vissa av dem använder instruktioner och begrepp som ännu inte har berörts i artiklarna i den här serien, andra (som rutiner för att arbeta med diskar) är nära besläktade med särdragen hos filsystemens struktur, men om du anser dig vara kompetent i dessa frågor kan du läsa den själv med deras implementeringar och förstå hur de fungerar. Med detta sagt är det viktigare att ta reda på hur man ringer dem från din egen kod:

  • lib_print_string - Tar en pekare till en nollterminerad sträng genom SI-registret och skriver ut den strängen till skärmen.
  • lib_input_string - Accepterar en pekare till en buffert genom SI-registret och fyller denna buffert med tecken som angetts av användaren med tangentbordet. Efter att användaren tryckt på Enter-tangenten avslutas raden i bufferten null och kontrollen återgår till den anropande programkoden.
  • lib_move_cursor - Flyttar markören på skärmen till positionen med koordinater passerade genom DH (linjenummer) och DL (kolumnnummer) registren.
  • lib_get_cursor_pos - denna subrutin bör anropas för att få numren på den aktuella raden och kolumnen genom DH- respektive DL-registren.
  • lib_string_uppercase - Tar en pekare till början av en nollterminerad sträng genom AX-registret och konverterar tecknen i strängen till versaler.
  • lib_string_length - Tar en pekare till början av en nollterminerad sträng via AX-registret och returnerar dess längd via AX-registret.
  • lib_string_compare - Tar pekare till början av två nollterminerade strängar genom SI- och DI-registren och jämför strängarna. Ställer in bärflaggan om linjerna är identiska (för att använda en greninstruktion beroende på jc carry-flaggan) eller rensar denna flagga om linjerna är olika (för att använda jnc-instruktionen).
  • lib_get_file_list - Tar en pekare till början av bufferten genom SI-registret och placerar en nollterminerad sträng i den bufferten som innehåller en kommaseparerad lista med filnamn från disken.
  • lib_load_file - tar en pekare till början av raden som innehåller filnamnet genom AX-registret och laddar innehållet i filen med den offset som passerar genom CX-registret. Returnerar antalet byte som kopierats till minnet (det vill säga storleken på filen) med hjälp av BX-registret, eller ställer in bärflaggan om en fil med det angivna namnet inte hittas.

Jag säger direkt, stäng inte artikeln med tankarna "Fan, ännu en Popov." Han har bara en slickad Ubuntu, och jag har allt från grunden, inklusive kärnan och applikationer. Så, fortsättningen under snittet.

OS-grupp: här.
Jag ska ge dig en skärmdump först.

Det finns inga fler av dem, och nu mer i detalj om varför jag skriver det.

Det var en varm aprilkväll, torsdag. Sedan barndomen drömde jag om att skriva ett OS, när jag plötsligt tänkte: "Nu vet jag fördelarna och nackdelarna, varför inte förverkliga min dröm?" Jag googlade sidor om detta ämne och hittade en artikel från Habr: "Hur man börjar och inte slutar skriva OS". Tack till författaren för länken till OSDev Wiki nedan. Jag gick dit och började jobba. Det fanns i en artikel all data om det lägsta operativsystemet. Jag började bygga cross-gcc och binutils och skrev sedan om allt därifrån. Du borde ha sett min glädje när jag såg inskriptionen "Hej, kärnvärlden!" Jag hoppade direkt från stolen och insåg – jag kommer inte att ge upp. Jag skrev "konsol" (inom citattecken, jag hade inte tillgång till tangentbordet), men sedan bestämde jag mig för att skriva ett fönstersystem. Som ett resultat fungerade det, men jag hade ingen tillgång till tangentbordet. Och så bestämde jag mig för att komma på ett namn baserat på X Window System. Googlade Y Window System - det är det. Som ett resultat döpte jag det till Z Window System 0.1, inkluderat i OS365 pre-alpha 0.1. Och ja, ingen såg henne förutom jag själv. Sedan kom jag på hur jag skulle implementera tangentbordsstöd. Skärmdump av den allra första versionen, när det fortfarande inte fanns något, inte ens fönstersystemet:

Textmarkören rörde sig inte ens i den, som du kan se. Sedan skrev jag ett par enkla Z-baserade applikationer. Och här kommer 1.0.0 alpha-versionen. Det fanns många saker, även systemmenyn. Och filhanteraren och kalkylatorn fungerade helt enkelt inte.

Jag blev direkt terroriserad av en vän som bara bryr sig om skönhet (Mitrofan, förlåt). Han sa "Vi fick ner det till VBE-läge 1024 * 768 * 32, vi fick ner det, vi fick ner det! Tja, drick ner det!" Nåväl, jag var redan trött på att lyssna på honom, och jag sköljde ändå ner det. Om implementeringen nedan.

Jag gjorde allt till min bootloader, nämligen GRUB, med den kan du ställa in det grafiska läget utan komplikationer genom att lägga till några magiska rader i Multiboot-huvudet.

Ställ ALIGN, 1<<0 .set MEMINFO, 1<<1 .set GRAPH, 1<<2 .set FLAGS, ALIGN | MEMINFO | GRAPH .set MAGIC, 0x1BADB002 .set CHECKSUM, -(MAGIC + FLAGS) .align 4 .long MAGIC .long FLAGS .long CHECKSUM .long 0, 0, 0, 0, 0 .long 0 # 0 = set graphics mode .long 1024, 768, 32 # Width, height, depth
Och sedan från Multiboot-informationsstrukturen tar jag framebuffer-adressen och skärmupplösningen och skriver pixlar där. VESA gjorde allt väldigt förvirrat - RGB-färger måste anges i omvänd ordning (inte R G B, utan B G R). I flera dagar förstod jag inte - varför pixlarna inte visas!? Som ett resultat insåg jag att jag glömde att ändra värdena för 16 färgkonstanter från 0 ... 15 till deras RGB-ekvivalenter. Som ett resultat släppte jag, samtidigt som jag sköljde ner gradientbakgrunden. Sedan gjorde jag en konsol, 2 applikationer och släppte 1.2. Åh ja, jag glömde nästan - du kan ladda ner OS på

Assemblerare

Assemblerare(från engelska. assemble - to assemble) - en kompilator från assemblerspråk till maskinspråkkommandon.
Det finns en assembler för varje processorarkitektur och för varje OS- eller OS-familj. Det finns även så kallade "cross-assemblers" som gör det möjligt att på maskiner med samma arkitektur (eller i miljön för ett OS) sätta ihop program för en annan målarkitektur eller ett annat OS, och få körbar kod i ett format som lämpar sig för exekvering på målarkitekturen eller i målmiljön. OS.

X86 arkitektur

Assemblers för DOS

De mest kända montörerna för DOS-operativsystemet är Borland Turbo Assembler (TASM) och Microsoft Macro Assembler (MASM). Den enkla A86-monteraren var också populär vid en tidpunkt.
Till en början stödde de bara 16-bitars instruktioner (före Intel 80386-processorn). Senare versioner av TASM och MASM stöder både 32-bitars instruktioner, såväl som alla instruktioner som introduceras i modernare processorer och arkitekturspecifika instruktionsuppsättningar (som MMX, SSE, 3DNow! Etc.) ...

Microsoft Windows

Med tillkomsten av operativsystemet Microsoft Windows dök TASM-tillägget, kallat TASM32, upp som gjorde det möjligt att skapa program för att köras i Windows-miljön. Den senaste kända versionen av Tasm är 5.3, som stöder MMX-instruktioner, och som för närvarande ingår i Turbo C ++ Explorer. Men officiellt stoppas utvecklingen av programmet helt.
Microsoft underhåller sin produkt som kallas Microsoft Macro Assembler. Det fortsätter att utvecklas till denna dag, med de senaste versionerna inkluderade i DDK:erna. Men versionen av programmet som syftar till att skapa program för DOS utvecklas inte. Dessutom har Stephen Hutchesson skapat ett MASM-programmeringspaket som heter "MASM32".

GNU och GNU/Linux

GNU-operativsystemet inkluderar gcc-kompilatorn, som inkluderar gassamlaren (GNU Assembler) som använder AT&T-syntax, till skillnad från de flesta andra populära sammanställare som använder Intel-syntax.

Bärbara montörer

Det finns också ett assemblerprojekt med öppen källkod, versioner av vilka finns tillgängliga för olika operativsystem, och som låter dig få objektfiler för dessa system. Denna assembler kallas NASM (Netwide Assembler).
YASM är en BSD-licensierad version av NASM omskriven från grunden (med vissa undantag).
FASM (Flat Assembler) är en ung montör under en BSD-licens modifierad för att förbjuda omlicensiering (inklusive under GNU GPL). Det finns versioner för KolibriOS, GNU / Linux, MS-DOS och Microsoft Windows, använder Intel-syntax och stöder AMD64-instruktioner.

RISC-arkitekturer


MCS-51
AVR
För närvarande finns det 2 kompilatorer från Atmel (AVRStudio 3 och AVRStudio4). Den andra versionen är ett försök att fixa den inte särskilt framgångsrika första. Det finns även en assembler i WinAVR.
ÄRM
AVR32
MSP430
PowerPC

Montering och sammanställning

Processen att översätta ett assemblerspråksprogram till objektkod kallas vanligtvis assembly. Till skillnad från kompilering är montering mer eller mindre entydig och reversibel. I assemblerspråk motsvarar varje mnemonic en maskininstruktion, medan i programmeringsspråk på hög nivå kan ett stort antal olika instruktioner döljas bakom varje uttryck. I princip är denna uppdelning ganska godtycklig, så ibland kallas översättning av monteringsprogram också för kompilering.

assembleringsspråk

assembleringsspråk- en typ av programmeringsspråk på låg nivå, vilket är ett format för inspelning av maskininstruktioner som är läsbart för människor. Ofta, för korthetens skull, kallas det helt enkelt en assembler, vilket inte är sant.

Instruktioner för sammansättningsspråk överensstämmer en-till-en med processorinstruktioner och representerar i själva verket en bekväm symbolisk notation (mnemonisk kod) av kommandon och deras argument. Monteringsspråket tillhandahåller också grundläggande programabstraktioner: länkar delar av programmet och data genom etiketter med symboliska namn (under monteringen beräknas en adress för varje etikett, varefter varje förekomst av etiketten ersätts med denna adress) och direktiv.
Assembler-direktiv tillåter dig att inkludera datablock (beskrivna explicit eller lästa från en fil) i programmet; upprepa ett visst fragment ett visst antal gånger; kompilera ett fragment villkorligt; ställ in exekveringsadressen för fragmentet, skild från adressen för platsen i minnet [specificera!]; ändra värdena på etiketter under kompilering; använda makron med parametrar osv.
Varje processormodell har i princip sin egen uppsättning instruktioner och motsvarande assemblerspråk (eller dialekt).

Fördelar och nackdelar

Församlingsspråkets dygder

Den minsta mängden redundant kod, det vill säga användningen av färre instruktioner och minnesåtkomster, gör att du kan öka hastigheten och minska storleken på programmet.
Att säkerställa full kompatibilitet och maximera kapaciteten för den önskade plattformen: med hjälp av speciella instruktioner och tekniska funktioner för denna plattform.
Vid programmering i assembler blir specialfunktioner tillgängliga: direkt åtkomst till hårdvara, I/O-portar och specialregister för processorn, såväl som möjligheten att skriva självmodifierande kod (det vill säga metaprogrammering och utan behov av programvara tolk).
Den senaste säkerhetstekniken implementerad i operativsystem tillåter inte att göra självmodifierande kod, eftersom de utesluter samtidig exekvering av instruktioner och skrivning i samma minnesområde (W ^ X-teknik i BSD-system, DEP i Windows).

Nackdelar med assemblerspråk

Stora mängder kod och ett stort antal ytterligare små uppgifter, vilket leder till att koden blir mycket svår att läsa och förstå, och därför svårare att felsöka och förfina programmet, samt svårigheten att implementera programmeringsparadigm och andra konventioner. vilket leder till komplexiteten i samarbetsutveckling.
Färre tillgängliga bibliotek, deras låga kompatibilitet med varandra.
Icke-portabilitet till andra plattformar (förutom binärkompatibla).

Ansökan

Följer direkt av för- och nackdelar.
Eftersom det är extremt obekvämt att skriva stora program på assemblerspråk är de skrivna på högnivåspråk. På assemblerspråk skriver de små fragment eller moduler för vilka de är avgörande:
prestanda (förare);
kodstorlek (startsektorer, programvara för mikrokontroller och processorer med begränsade resurser, virus, programvaruskydd);
specialfunktioner: arbeta direkt med hårdvara eller maskinkod, det vill säga operativsystemladdare, drivrutiner, virus, skyddssystem.

Länkar sammansättningskod till andra språk

Eftersom för det mesta bara programfragment skrivs i assembly, måste de länkas till resten av koden på andra språk. Detta uppnås på två huvudsakliga sätt:
Vid sammanställningstid- infogning av inline assemblerfragment i programmet genom särskilda språkdirektiv, inklusive skrivprocedurer på assemblerspråk. Metoden är bekväm för enkla datatransformationer, men fullfjädrad assembly-kod, med data och subrutiner, inklusive subrutiner med många ingångar och utgångar som inte stöds av högnivåspråk, kan inte göras med den.
På byggstadiet, eller separat sammanställning. För samverkan mellan de länkade modulerna är det tillräckligt att bindningsfunktionerna stöder de erforderliga anropskonventionerna och datatyperna. Individuella moduler kan skrivas på alla språk, inklusive assembler.

Syntax

Det finns ingen allmänt accepterad standard för syntaxen för assemblerspråk. Det finns dock standarder som de flesta assemblerspråkutvecklare följer. De viktigaste sådana standarderna är Intel-syntax och AT&T-syntax.

Instruktioner

Det allmänna formatet för skrivinstruktioner är detsamma för båda standarderna:

[etikett:] opcode [operander] [; kommentar]

där opkoden är den faktiska mnemoniken för instruktionen till processorn. Prefix (upprepningar, ändra typ av adressering, etc.) kan läggas till den.
Operanderna kan vara konstanter, registernamn, adresser i RAM, etc. Skillnaderna mellan Intel och AT&T-standarder hänför sig främst till uppräkningsordningen för operander och deras syntax för olika adresseringsmetoder.
Mnemoniken som används är vanligtvis densamma för alla processorer med samma arkitektur eller familj av arkitekturer (bland de allmänt kända är mnemonics från Motorola, ARM, x86-processorer och kontroller). De beskrivs i processorspecifikationen. Möjliga undantag:
Om assemblern använder AT&T-syntax på flera plattformar (original mnemonics tvingas till AT&T-syntax)
Om det från början fanns två standarder för inspelning av minnesminnen (kommandosystemet ärvdes från en processor från en annan tillverkare).
Till exempel ärvde Zilog Z80-processorn Intel i8080-instruktionsuppsättningen, utökade den och ändrade mnemonics (och registerbeteckningar) på sitt eget sätt. Till exempel ändrade jag Intels mov till ld. Motorola Fireball-processorerna ärvde Z80-instruktionsuppsättningen och skar ner den något. Samtidigt har Motorola officiellt återvänt till Intel mnemonics. Och för tillfället arbetar hälften av Fireball-montörerna med Intel-mnemonics och hälften med Zilog-mnemonics.

direktiv

Förutom instruktioner kan ett program innehålla direktiv: kommandon som inte direkt översätts till maskininstruktioner, utan styr kompilatorns funktion. Deras uppsättning och syntax varierar avsevärt och beror inte på hårdvaruplattformen, utan på kompilatorn som används (som ger upphov till dialekter av språk inom samma familj av arkitekturer). Som en "gentleman's set" av direktiv kan man peka ut:
datadefinition (konstanter och variabler)
hantera organisationen av programmet i minnet och parametrarna för utdatafilen
ställa in kompileringsläget
alla typer av abstraktioner (dvs element av högnivåspråk) - från design av procedurer och funktioner (för att förenkla implementeringen av det procedurmässiga programmeringsparadigmet) till villkorliga konstruktioner och loopar (för det strukturerade programmeringsparadigmet)
makron

Exempel på program

Ett exempel på Hello world-program för MS-DOS för x86-arkitektur på TASM-dialekten:

.MODEL LITEN KOD SEGMENT ANSUM CS: KOD, DS: KOD ORG 100h START: mov ah, 9 mov dx, OFFSET Msg int 21h int 20h Msg DB "Hello World", 13,10, "$" KOD SLUTAR SLUT START START

Ursprung och kritik av termen "sammansättningsspråk"

Denna typ av språk fick sitt namn från namnet på översättaren (kompilatorn) från dessa språk - assembler (engelsk assembler). Namnet på den senare beror på det faktum att det på de första datorerna inte fanns några språk på högre nivå, och det enda alternativet till att skapa program med hjälp av assembler var programmering direkt i koder.
Monteringsspråket på ryska kallas ofta "assembler" (och något relaterat till det är "assembler"), vilket enligt den engelska översättningen av ordet är fel, men passar in i det ryska språkets regler. Men själva assemblern (programmet) kallas också helt enkelt "assembler" och inte "assembler-språkkompilator" etc.
Användningen av termen "sammansättningsspråk" kan också leda till missuppfattningen att det finns ett enda lågnivåspråk, eller åtminstone en standard för sådana språk. När man namnger språket som ett specifikt program är skrivet på, är det lämpligt att specificera för vilken arkitektur det är avsett och på vilken dialekt av språket det är skrivet.

Nyligen bestämde jag mig för att lära mig assembler, men jag var inte intresserad av att slösa bort rader med kod. Jag tänkte att när jag lär mig assembler kommer jag att behärska något ämnesområde. Så mitt val föll på att skriva en bootloader. Resultatet av mina fynd här på bloggen.

Jag skulle genast vilja säga att jag älskar teori i kombination med praktik, så låt oss börja.

Först ska jag visa dig hur du skapar det enklaste MBR så att vi kan njuta av resultatet på kortast möjliga tid. När vi komplicerar de praktiska exemplen kommer jag att ge teoretisk information.

Låt oss först göra en bootloader för USB-minnet!

Uppmärksamhet!!! Vårt första assemblerprogram kommer att fungera både för ett USB-minne och för andra enheter som diskett eller hårddisk. Därefter, för att alla exempel ska fungera korrekt, kommer jag att ge ett antal förtydliganden angående hur koden fungerar på olika enheter.

Vi kommer att skriva vidare Fasm, eftersom det är han som anses vara den bästa kompilatorn för att skriva loaders, vilket är MBR. Det andra skälet till att välja Fasm är att det avsevärt förenklar processen att kompilera filer. Inga kommandoradsdirektiv etc. nonsens som helt kan avskräcka lärande assembler och uppnå våra mål. Så i det inledande skedet behöver vi två program och några onödigt USB-minne av minsta storlek. Jag grävde upp 1Gb på mig själv (det formateras snabbt, och det är inte synd om något). Efter arbetet med vår bootloader kommer flashenheten att sluta fungera normalt. min Windows 7 vägrar att formatera min sticka. Jag råder dig att återställa flash-enheten till liv med verktyget HP USB Disk Storage Format Tool ( HPUSBFW.EXE) eller andra verktyg för att formatera flash-enheter.

Vi installerar dem och slänger ut lämpliga genvägar på skrivbordet eller var du vill.

Förberedelserna är över, låt oss börja

Öppna Fasmw.exe och skriv följande där. Vi skisserar det absoluta minimum av kod för att se resultatet. Senare kommer vi att analysera vad som är glödande här. Kort sagt, jag ger kommentarer.

FASM-kod: ============= boot.asm ===============

org 7C00h; adresserna till vårt program beräknas med hänsyn till detta direktiv

användning16; en hexadecimal kod genereras

cli; inaktivera avbrott för att ändra adresser i segmentregister

mov yxa, 0

mov sp, 7C00h

sti; aktivera avbrott (efter adressändring)

mov ax, 0003h; inställning av videoläge för att visa en linje på skärmen

int 10h

mov axe, 1301h; visar faktiskt strängfunktionen 13h int 10h (mer om detta senare)

mov bp, stroka; adress för utgångslinjen

mov dx, 0000h; rad och kolumn där texten visas

mov cx, 15; antal tecken i utdatasträngen

mov bx, 000eh; 00-videos sidnummer (bättre att inte röra) 0e-teckenattribut (färg, bakgrund)

int 10h

jmp $; markera tid (slingor programmet vid denna punkt)

stroka db "Ok, MBR laddad!"

gånger 510 - ($ - $$) db 0; fylla med nollor gapet mellan föregående byte och den sista

db 0x55, 0xAA; sista två byte

Kompilera den här koden (Ctrl + F9) i fasm "e och spara den resulterande binära filen som boot.bin på ett bekvämt ställe. Innan du skriver vår binära fil till ett USB-minne, en liten teori.

När du kopplade in USB-minnet i datorn är det absolut inte självklart för BIOS-systemet att du vill starta från USB-minnet, så i BIOS-inställningarna måste du välja den enhet du vill starta från. valde att boota från USB (du måste ta reda på hur du gör detta själv, eftersom BIOS-gränssnittet har olika varianter ... du kan googla BIOS-inställningarna för ditt moderkort. Det är inget komplicerat, som regel).

Nu när BIOS vet att du vill starta från flashenheten måste den se till att nollsektorn på flashenheten är startbar. För att göra detta ser BIOS ut de sista två byten av nollsektorn och, om de är lika med 0x55 0xAA, kommer det först då att laddas in i RAM. Annars kommer BIOS bara att passera din flashenhet. Efter att ha hittat dessa två magiska bytes, laddar han nollsektorn i RAM-minnet på adressen 0000: 7C00h, och glömmer sedan USB-minnet och överför kontrollen till denna adress. Nu tillhör all makt över datorn din bootloader och den kan, redan från RAM-minnet, ladda ytterligare kod från USB-minnet. Nu ska vi se hur just denna sektor ser ut i DMDE-programmet.

1. Sätt i ditt USB-minne i datorn och se till att det inte innehåller den information du behöver.

2.Öppna DMDE-programmet. Läs alla ytterligare åtgärder i figurerna:

Efter att ha sett denna serie kommer du att kunna ladda din MBR på ett USB-minne. Och så här ser det efterlängtade resultatet av vår bootloaders arbete ut:


Förresten, om vi pratar om den minimala bootloader-koden, kan det se ut så här:

Org 7C00h
jmp $
db 508 dup (0)
db 0x55,0xAA

En sådan starthanterare, efter att ha fått kontroll, lägger helt enkelt på datorn och kör ett meningslöst kommando jmp $ i en loop. Jag kallar henne markeringstid.

Jag har lagt upp en video på YouTube som kan hjälpa dig:

Till sist, några snabba fakta om bootloadern:

1. Bootloader, även känd som bootloader, även känd som MBR, är 512 byte stor. Historiskt sett,
att detta villkor måste uppfyllas för att stödja äldre media och enheter.
2. Laddaren är alltid placerad i nollsektorn av en flashenhet, diskett, hårddisk, från DMDE-programmets eller andra hex-redigerares synvinkel som låter dig arbeta med enheter. För att ladda ner binären (vår boot.bin) till en av de listade enheterna behöver vi inte tänka på deras interna fysiska struktur. DMDE-programmet vet bara hur man läser sektorer på dessa enheter och visar dem i LBA-läge (det bara numrerar dem från 0 till den sista sektorn). Du kan läsa om LBA
3. Starthanteraren måste alltid sluta med två byte 0x55 0xAA.
4. Laddaren laddas alltid i minnet på adressen 0000: 7C00h.
5. Operativsystemet börjar med starthanteraren.


Idag finns ett märkligt exempel i vårt Curiosities Cabinet - ett operativsystem skrivet i ren assembler. Tillsammans med drivrutiner, ett grafiskt skal, dussintals förinstallerade program och spel tar det mindre än en och en halv megabyte. Möt det exceptionellt snabba och övervägande ryska operativsystemet "Kolibri".

Utvecklingen av "Kolibri" gick ganska snabbt fram till 2009. Fågeln har lärt sig att flyga på olika hårdvara, som kräver minst av den första Pentium och åtta megabyte RAM. Minsta systemkrav för Hummingbird är:

  • CPU: Pentium, AMD 5x86 eller Cyrix 5x86 utan MMX @ 100 MHz;
  • RAM: 8 MB;
  • grafikkort: VESA-kompatibelt med stöd för VGA-läge (640 × 480 × 16).

Modern "Hummingbird" är en regelbundet uppdaterad "night build" av den senaste officiella versionen, släppt i slutet av 2009. Vi testade build 0.7.7.0+ från 20 augusti 2017.

VARNING

I standardinställningarna har KolibriOS inte tillgång till diskar som är synliga via BIOS. Tänk noga och gör en säkerhetskopia innan du ändrar den här inställningen.

Förändringar i nattliga byggnader, även om de är små, har ackumulerats tillräckligt under åren. Den uppdaterade "Kolibri" kan skriva till FAT16–32 / ext2 - ext4-partitioner och stöder andra populära filsystem (NTFS, XFS, ISO-9660) i läsläge. Det lade till stöd för USB och nätverkskort, lade till en TCP / IP-stack och ljudkodekar. I allmänhet kan du redan göra något i det, och inte bara titta en gång på ett ultralätt operativsystem med ett GUI och bli imponerad av lanseringshastigheten.



Liksom de tidigare versionerna är den senaste "Kolibri" skriven i flat assembler (FASM) och upptar en diskett - 1,44 MB. Tack vare detta kan den helt och hållet lokaliseras i något slags specialiserat minne. Till exempel skrev hantverkarna KolibriOS direkt in i Flash BIOS. Under drift kan den vara helt placerad i cachen hos vissa processorer. Föreställ dig bara: alla operativsystem, tillsammans med program och drivrutiner, är cachade!

INFO

När du besöker webbplatsen kolibrios.org kan din webbläsare varna dig för faran. Anledningen verkar vara assemblerprogrammen i distributionen. VirusTotal definierar nu webbplatsen som helt säker.

"Kolibri" kan enkelt laddas från en diskett, hårddisk, flashenhet, Live CD eller i en virtuell maskin. För emulering räcker det att ange typen av OS "annat", allokera en processorkärna och lite RAM till den. Det är inte nödvändigt att ansluta disken, och om du har en router med DHCP kommer "Kolibri" omedelbart att ansluta till Internet och det lokala nätverket. Direkt efter att du laddat kommer du att se ett motsvarande meddelande.


Ett problem är att HTTPS-protokollet inte stöds av den inbyggda Kolibri-webbläsaren. Därför var det inte möjligt att titta på webbplatsen i den, liksom att öppna sidorna på Google, Yandex, Wikipedia, "Sberbank" ... faktiskt, ingen bekant adress. Alla gick över till ett säkert protokoll för länge sedan. Den enda sidan med gammaldags ren HTTP som jag stötte på är "den ryska regeringens portal", men den såg inte den bästa ut i en textwebbläsare heller.



Utseendeinställningarna i Hummingbird har förbättrats under åren, men är fortfarande långt ifrån idealiska. Listan över videolägen som stöds visas på Kolibri-laddningsskärmen genom att trycka på knappen med den latinska bokstaven a.



Listan över tillgängliga alternativ är begränsad och den nödvändiga upplösningen kanske inte visas. Om du har ett grafikkort med AMD (ATI) GPU kan du direkt lägga till anpassade inställningar. För att göra detta måste du skicka parametern -m till ATIKMS-lastaren x x , till exempel:

/ RD / 1 / DRIVERS / ATIKMS -m1280x800x60 -1

Här / RD / 1 / DRIVERS / ATIKMS är sökvägen till starthanteraren (RD - RAM-disk).

När systemet körs kan det valda videoläget ses med kommandot vmode och (teoretiskt) växlas manuellt. Om "Kolibri" körs i den virtuella maskinen, kommer detta fönster att förbli tomt, men med en ren uppstart kan Intel-videodrivrutiner läggas till från i915 till Skylake inklusive.

Överraskande nog passar ett gäng spel in i KolibriOS. Bland dem finns logik- och arkadspel, tagg, orm, tankar (nej, inte WoT) - ett helt "Game Center"! Till och med Doom och Quake portades till Hummingbird.



En annan viktig sak var FB2READ-läsaren. Det fungerar korrekt med kyrilliska och har textvisningsinställningar.



Jag rekommenderar att du lagrar alla användarfiler på ett USB-minne, men det måste anslutas via en USB 2.0-port. Vår USB 3.0-flashenhet (i USB 2.0-porten) med en kapacitet på 16 GB med NTFS-filsystemet identifierades omedelbart. Om du behöver skriva filer bör du ansluta ett USB-minne med en FAT32-partition.



Kolibri-distributionen inkluderar tre filhanterare, verktyg för att visa bilder och dokument, ljud- och videospelare och andra anpassade applikationer. Däremot fokuserar den på utveckling av assemblerspråk.



Den inbyggda textredigeraren har ASM-syntaxmarkering och låter dig till och med köra maskinskrivna program direkt.



Bland utvecklingsverktygen finns Oberon-07/11-kompilatorn för i386 Windows, Linux och KolibriOS, samt lågnivåemulatorer: E80 - ZX Spectrum-emulator, FCE Ultra - en av de bästa NES-emulatorerna, DOSBox v.0.74 och andra . Alla av dem var speciellt portade till Hummingbird.

Om du lämnar KolibriOS i några minuter kommer skärmsläckaren att starta. Kodrader kommer att köras på skärmen, där du kan se en referens till MenuetOS.

Fortsättning är endast tillgänglig för deltagare

Alternativ 1. Gå med i "site"-gemenskapen för att läsa allt material på sajten

Medlemskap i communityn inom den angivna perioden ger dig tillgång till ALLT Hackers material, ökar din personliga kumulativa rabatt och låter dig samla ett professionellt Xakep-resultat!







2021 gtavrl.ru.