Programmieren
 
 
 
 
 
C -Grundkurs
 
 
 
 
 
Inlineassembler
 
 
.. Grundlagen
 
 

 
 
 
 
 
Inlineassembler (Assemblercode im C -Programm)
Es ist möglich, den Quelltext eines C-Programms mit Sprachbrocken der Assemblersprache zu garnieren. Normaler Weise übersetzt bei C ein Compiler alle C-Befehlsworte in die Maschinensprache. Die Assembler-Programmanteile dagegen, müssen durch ein Assemblerprogramm übersetzt werden. Ein solches muß natürlich in der IDE vorhanden sein. Der Linker bindet beide Programmteile zusammen und die CPU bekommt die Ursprungs-Programmiersprache nicht mehr mit, denn beide Teile bestehen aus Maschinensprache.

asm {}
Der C -Schlüsselbefehl zum Übergang von der C- zur Assembler-Programmierung ist der Befehl asm{}. Er beendet das Compilerprogramm und startet das Assemblerprogramm, so eines vorhanden ist. Nach seiner zweiten Blockklammer } wird das Assemblerprogramm beendet und wieder das Compilerprogramm gestartet. Seine Mnemonik lautet ..
                               asm {..}
Zwischen seinen geschweiften Klammern stehen die Assemblerbefehle. Wird nur ein einziger Assemblerbefehl genannt können die geschweiften Klammern entfallen.

In den Assemblerbefehlen können C-Variable benutzt werden, wenn Sie von einem Typ sind, der auch in die Register der CPU hineinpasst. Doch zunächst ein Blick auf die Namen und Speichergrössen der CPU-Register ...

Die Register der 80x86 CPU
Eine umfassende Beschreibung dieser Register befindet sich im Kapitel über die Assembler-Programmierung der Intel 80x86 CPU im Real Mode unter ..
[Register der 80x86 CPU]

Für einen schnellen Überblick reicht es, die Namen der Allzweck-Benutzer -Register zu kennen. Ihre Namen lauten A, B, C, und D. Ein zusätzlicher Buchstabe zu den Namen gibt an, mit wie vielen Bits die Register benutzt werden sollen.

                                                 Allzweckregister der ALU
                                                 -----------------------------
                                                 (16 Bit)    AX, BX, CX, DX
                                                 (  8 Bit)    AH-AL, BH-BL, CH-CL, DH-DL

X gibt an, dass das entsprechende Register mit 16 Bit in einem Assemblerbefehl benutzt werden soll, während H und L für high und low stehen und das genannte Register in zwei unabhängige 8Bit-Portionen teilt.
Da beide Portionen das gleiche X-Register betreffen ergibt sich zudem folgende Kuriosität. Besitzt das AH-Register den Wert AFh und das AL-Register den Wert FEh, so ist das AX-Register mit dem Wert AFFEh gefüllt. Je nach Assemblerbefehl kann man folgend nur das AH oder das AL oder das AX -Register benutzen.

                                                 C-Variablentypen für die Register
                                                 --------------------------------------
                                                 (16 Bit)    unsigned oder int
                                                 (  8 Bit)    signed char oder unsigned char

Die C-Variablentypen müssen zwingend der Größe der CPU-Register entsprechen. Sie dürfen weder größer noch kleiner sein. So kann ein X-Register mit 16 Bit nur unsigned oder int Variable aufnehmen. Nicht jedoch char oder long.
 
 
 
 
 
Das nachfolgende Programm zeigt, wie Inlineassembler und C- Programmteile miteinander kombiniert werden können. Dabei sind die Inlineassembler -Teile in einer C-Funktion untergebracht, welche nach den bekannten Methoden zwei Parameter übergeben bekommt und einen Parameter zurückgibt.

Beispiel einer Funktion in Inlineassembler:        malnehmen()
 

 


 

#include <stdio.h>
#include <conio.h>

// malnehmen()
// Inlineassembler -Funktion, die zwei vorzeichenbehaftete Bytes
// (C-Typ: signed char, 1Byte) multipliziert und in einem Word
// (C-Typ: int, 2Byte)zurückgibt

int malnehmen(signed char fakt1, signed char fakt2)
{
 asm{ mov AL, fakt1      // Register AL mit dem Faktor fakt1 laden
      imul fakt2    }    // und mit Faktor fakt2 multiplizieren
 return(_AX);            // Ergebnis in Register AX zurückgeben
}

// Beispielaufruf von malnehmen

void main(void)
{
 signed char x=-3, y=6;
 int    erg = 0;

 erg = malnehmen(x,y);
 printf("Das Ergebnis von %d * %d = %d\r\n", x, y, erg);
 getch();
}

 


 

 

Bildschirmausgabe

 

Das Ergebnis von -3 * 6 = -18
 
 
 
 
Besonderheit des Programms
Mit dem Schlüsselwort return() wird wieder die Sprache C verwendet. Der Ausdruck _AX stellt eine Möglichkeit dar, unmittelbar aus der Sprache C heraus, auf die Register der CPU zuzugreifen. Diese werden in C grundsätzlich mit großen Buchstaben geschrieben, vor denen ein Unterstrich steht.
Alternativ hätte man also das Ergebnis in AX auch erst per Assemblersprache einer C-Variablen zuweisen und diese dann zurückgeben können. Das Programm hätte dann folgend ausgesehen ..
 
 
 
int malnehmen(signed char fakt1, signed char fakt2)
{
 int erg = 0; 
 asm{ mov AL, fakt1    // Register AL mit dem Faktor fakt1 laden
      imul fakt2       // und mit Faktor fakt2 multiplizieren
      mov erg, AX }    // Ergebnis in Register AX nach C-Variable
 return(erg);          // Ergebnis in der C-Variablen zurückgeben
}
 

 


 

 
 
 
 
Assemblerbefehle
Die Inlineassembler -Funktion basiert auf den Assemblerbefehlen mov und imul. Deren Beschreibung ist im Kapitel über die Assembler 8086 Programmierung unter .. [Befehlssatz der CPU 80x86] zu finden. mov bedeutet bewege nach und imul ist ein Multiplikationsbefehl für Ganze Zahlen. Die übergebenen C-Variablen werden also durch Assemblerbefehle multipliziert und das Ergebnis in einer C-Variablen zurückgegeben.
Der mov-Assemblerbefehl baut sich dabei wie alle Assemblerbefehle mit zwei Parametern wie folgt auf ..
                                      Befehl Ziel, Quelle
                                      -------------------------
                                      mov     AL,   fakt1
                                      mov     erg,   AX

.. also, bewege (kopiere) nach Register AL den Inhalt der C-Speicherzelle fakt1 bzw. bewege nach der C-Speicherzelle erg, den Inhalt des CPU-Registers AX.
Es ist zu beachten, dass am Ende der Assemblerzeilen kein Semikolon steht und bei Assemblerbefehlen die Groß- / Kleinschreibweise keine Rolle spielt. Es sei denn es werden C-Variable genannt.
 
 
 
 
 

 

 
 
 
 
 
Softwareinterrupts und der Assemblerbefehl int x
Softwareinterrupts (kurz SoftINT) sind indirekte Sprünge zu Unterprogrammen, die sich im BIOS, dem Betriebssystem oder in Anwenderprogrammen (häufig als Treiberprogramme bezeichnet) befinden. Sie werden durch den Assemblerbefehl int x ausgelöst wobei das x für eine Zahl zwischen 0 und 255 (0h-FFh) steht. Indirekt bedeutet, dass die Orte (Adressen) an denen die Unterprogramme beginnen, nicht unmittelbar von der CPU aufgerufen werden, sondern dass diese zuvor in einem Satz von vier RAM-Speicherzellen nachsehen muss, wo sich das Unterprogramm überhaupt befindet.
                                   1. es gibt 256 Softwareinterrupts die durch den Befehl int x
                                      (x=0-255) erreicht werden können

Die Anfangsadresse des aufzurufenden Unterprogramms ist also der Inhalt von vier Bytes, die sich im Arbeitsspeicher des Computers befinden. Bei der Möglichkeit 256 solcher Sprünge angeben zu können, müssen im Arbeitsspeicher also 4*256 RAM-Speicherbytes reserviert sein, damit die Adressen für 256 Unterprogramm hineinpassen. Die hierfür reservierten Bytes sind an dessen Anfang untergebracht und besitzen die Adressen 0-1023 (0-3FFh). Ihre Inhalte die ja wiederum Adressen sind, die auf Unterprogramme zeigen, bezeichnet man entsprechend als Zeiger oder Vektoren.
                                   2. Die 256 Softwareinterrupt-Vektoren sind in den RAM-
                                        Speichern 0-3FFh des Arbeitsspeichers einer Intel-CPU
                                        untergebracht.

Die Softwareinterrupts stellen eine elegante Möglichkeit der Intel-CPUs dar um interessante Unterprogramme des BIOS oder des Betriebssystems bei Multitask-Systemen aufrufen zu können, ohne zu wissen, wo sich diese dort befinden. Natürlich besteht eine übergreifende Absprache zwischen den Systemprogrammierern, welcher Softwareinterrupt benutzt werden muss um an diese unbekannten Programmorte zu gelangen. Die Softwareinterrupts sind in den Dokumentationen zu der Betriebssoftware beschrieben.
Die letzte Frage, wie denn die Adressvektoren, nach dem Start eines Computers, in die leeren RAM-Speicher 0-3FFh gelangen, ist einfach beantwortet. Sie werden vom BIOS oder dem Betriebssystem in seiner Initialisierungsphase (also beim Hochlaufen des Systems) dort hineinkopiert.
Mehr zu den SoftINTs im Kapitel: [Interrupts der CPU 80x86]
 
 
 
 
 
Das nachfolgende Programm demonstriert den Aufruf eines Unterprogramms im Betriebssystem mit Hilfe eines SoftINT.
Betriebssystem können die aktuelle Uhrzeit ermitteln. Diese basiert auf der Zeit einer batteriegebufferten Echtzeituhr (RTC -RealTimeClock) die auf dem Mainboard aufgebaut ist. Beim Start des Betriebssystems wird die RTC abgefragt und dann fortgesetzt. Die Abfrage der Zeit im Betriebssystem kann durch den Softwarinterrupt 21h geschehen, wenn man zuvor das AH-Register der CPU mit dem Wert 2Ch lädt.

Beispiel, Aufruf des SoftINT 21h, Funktion AH=2Ch
 
 

 
 
#include <stdio.h>                // für printf()
#include <conio.h>                // für getch()


// globale Variablen, welche die Rückgaben aufnehmen sollen
unsigned char stunde, minute, sekunde;

// holezeit()
// Funktion zum Aufruf des Softwareinterrupts INT 21h und seines
// Unter-Unterprogramms AH=2Ch, 'hole die Zeit des Betriebssystems'
// Die Zeit wird von den CPU-Registern CH,CL,DH in die globalen
// C-Variablen übertragen.
void holezeit(void)
{
 asm{ mov AH, 2Ch                  // Wahl des Unter-Unterprogramms
      int 21h                      // im SoftINT 21h und Aufruf
                                   // Rückgaben ..
      mov stunde,  CH              // Stunde  0-23 in CH
      mov minute,  CL              // Minute  0-59 in CL 
      mov sekunde, DH }            // Sekunde 0-59 in DH
}

// Testprogramm main()..
// ruft die Funktion holezeit() auf und gibt die globalen C-
// Variablen 'Stunde', 'Minute' und 'Sekunde' im Format
// std:min:sec zum Bildschirm aus.

void main (void)
{
  holezeit();
  printf("\n%0.2d:%0.2d:%0.2d",stunde,minute,sekunde);
  getch();
}
 
 

 

 

Bildschirmausgabe (Beispiel)

 

17:33:14
 
 
 
 
Besonderheit des Programms
Es gibt 256 Softwareinterrupts, aber weit mehr interessante Unterprogramme im BIOS und dem Betriebssystem. Die Systemprogrammierer haben sich darauf geeinigt mehr Unterprogramme zugänglich zu machen, indem sie Softwareinterrupt-Programm als Verteilerprogramme programmieren. Bei diesen wird durch einen Wert im AH-Register der CPU zu Unter-Unterprogrammen verzweigt. Auf diese Weise wächst die Anzahl der theoretisch erreichbaren Unterprogramme auf 256*256 = 65535 an.

Weitere Beispiele unter [Software Interrupts / C und Inlineassembler]
 
 
 
 
 

 
 
Regeln im Überblick
  • Assemblerprogramm
    Um die Inlineassembler -Programmierung nutzen zu können, muss die C-IDE über ein Assemblerprogramm verfügen. Es gibt Entwicklungsumgebungen die hierüber nicht verfügen. Dann ist die die Inlineassembler -Programmierung nicht möglich.
     
  • Zahlenangaben
    Zahlen ohne Kennung werden als Dezimalzahlen betrachtet. Hexadezimale Zahlen müssen mit einer Ziffer beginnen und die Kennung h tragen ( z.B.: 34h oder 0A4h nicht A4h). Die C-Schreibweise für hexadezimale Zahlen 0xA4 ist nicht zugelassen. In Assemblerbefehlen können binäre Zahlen verwendet werden. Sie erhalten als Kennung ein b. (z.B 00110100b =34h). Die letzte Möglichkeit ist die Angabe von ASCII-Zeichen zwischen Hochkommatas. Diese werden in den ASCII-Code übersetzt. (z.B.: '4' = 34h)
     
  • Kommentare
    Kommentare werden bei der Inlineassembler -Programmierung nach C-Art geschrieben. Das Semikolon ; darf nicht benutzt werden
     
  • Label
    Assember -Sprungbefehle dürfen nur auf ein C-Label gerichtet sein. Die anderen Assemblerbefehle dürfen nicht auf C-Labels zugreifen. (Diese können aber C-Variable benutzen)
     
  • CPU angeben
    Bei der Programmierung von Intel 80x86 Prozessoren sollte die CPU spezifiziert sein deren Assemblervorrat benutzt werden soll. Dies kann durch die Anweisung #pragma option -1 geschehen. In diesem Fall werden nur Maschinenbefehle erzeugt, mit denen die CPUs 80186 oder 80286 umgehen können. Neuere Prozessoren verfügen über umfangreichere Befehlssätze, die von der 80186 CPU nicht verstanden werden. Weitere CPUs können der 80386, 80486, Pentium usw. sein.
     
  • Geschützte Register
    Zum Abschluss einer Inlineassembler -Funktion müssen die Register BP, SP; CS, DS und SS unverändert vorliegen. Die anderen Register können beliebige Werte enthalten. Es ist also notwendig, dass die genannten Register auf dem Stack gesichert werden, wenn mit ihnen Operationen durchgeführt werden sollen, die sie verändern. Hierzu können die Assemblerbefehle PUSH und POP verwendet werden.
 
 
 
 
www..de