Experiment zu den Grundlagen des Computersystems – Darstellung auf Maschinenebene in Hochsprachen

Experiment 4 Darstellung von Hochsprachen auf Maschinenebene

Experimentnummer: 4 Experimentname: Darstellung von Hochsprachen auf Maschinenebene
Anwendbare Hauptfächer: Softwareentwicklung Kreditstunden: 2 Kreditstunden

1. Zweck des Experiments

1. Verstehen Sie die Darstellung von Funktionsaufrufen auf Maschinenebene.
2. Verstehen Sie die Darstellung komplexer Datenstrukturen auf Maschinenebene.
3. Verstehen Sie die Darstellung von Verzweigungs- und Schleifenanweisungen auf Maschinenebene.

2. Experimentelle Anforderungen

Schreiben Sie das Programm entsprechend den Anforderungen des Versuchsthemas und debuggen Sie es auf der Maschine

3. Experimentelle Ausrüstung und Umgebung

Computer, Windows 7, Visual C++ 6.0

4. Experimentelle Schritte und Inhalte

Schreiben Sie ein C-Sprachprogramm, das Schleifenverzweigungen und Prozeduraufrufe enthält. Überprüfen und analysieren Sie den Assemblercode. Im Folgenden finden Sie eine Referenz auf das Programm, das geschrieben werden kann.
Definieren Sie ein Array int sum[5];
geben Sie 5 Zahlen vom Bildschirm in die Array-Summe ein, indem Sie eine Schleife durchlaufen, und
rufen Sie die Funktion int Sum(int sum[]) auf, um die Summe aller Elemente im Array zu berechnen und zurückzugeben,
wenn der Rückgabewert lautet größer als 50, der Ausgabedurchschnittswert ist größer als 10, andernfalls ist der Durchschnittswert der Ausgabe kleiner als 10.
Überprüfen Sie nach bestandener Kompilierung den Assemblercode und schreiben Sie einen Bericht, um die folgenden Probleme in Kombination mit der C-Sprache zu beschreiben Programm, das du geschrieben hast:

  1. Wie wird der Assembler-Code des Funktionsaufrufs beschrieben und an welcher Adresse werden die Parameter platziert (es kann davon ausgegangen werden, dass die Adressen von %ebp und %esp bekannt sind)
  2. Assembly-Beschreibungsmethode des Arrays
  3. Assembly-Beschreibungsmethode der Branch-Anweisung
  4. Die Assembly-Beschreibungsmethode des Zyklus

Tipp:
Sehen Sie sich die Assembler-Code-Methode an:
Kompilieren Sie
den Haltepunkt F11 und legen Sie ihn fest, oder klicken Sie mit der rechten Maustaste auf den Haltepunktpfeil in Build -> Debug starten -> Step Into
, und gehen Sie zur Disassemblierung

C-Sprachcode

#include <stdio.h>

int g(int x)
{
    
    
  return x + 99;
}
int f(int x)
{
    
    
  return g(x);
}

void printStar(){
    
    
    for(int i = 0;i < 10; i++)
    {
    
    
        printf("*");
    }
}

int main() {
    
    
    int a = 0;
    int b = 0;
    int op1 = 1, op2 = 2;
    if (op1 = op2) {
    
    
        a = 1;
        b = 2;
    }
    return f(22) + 36;
}

Assembler-Code

g:
        pushl   %ebp
        movl    %esp, %ebp
        movl    8(%ebp), %eax
        addl    $99, %eax
        popl    %ebp
        ret
f:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $4, %esp
        movl    8(%ebp), %eax
        movl    %eax, (%esp)
        call    g
        leave
        ret
printStar:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $40, %esp
        movl    $0, -12(%ebp)
        jmp     .L6
.L7:
        movl    $42, (%esp)
        call    putchar
        addl    $1, -12(%ebp)
.L6:
        cmpl    $9, -12(%ebp)
        jle     .L7
        leave
        ret
main:
        pushl   %ebp
        movl    %esp, %ebp
        andl    $-16, %esp
        subl    $528, %esp
        movl    $0, 524(%esp)
        movl    $0, 520(%esp)
        movl    $1, 516(%esp)
        movl    $2, 512(%esp)
        movl    516(%esp), %eax
        cmpl    512(%esp), %eax
        jne     .L9
        movl    $1, 524(%esp)
        movl    $2, 520(%esp)
.L9:
movl    $66, 24(%esp)
        movl    $666, 28(%esp)
        call    printStar
        movl    $22, (%esp)
        call    f
        addl    $36, %eax
        leave
        ret

Code-Erklärung:

push %ebp, movl %esp, %ebpZwei Codezeilen, schieben Sie ebp auf den Stapel
und weisen Sie dann ebp den Wert von esp zu. ebp zeigt auf dieselbe Position wie esp
andl $-16,%esp. Durch die Operation von esp und 0xfffffff0 wird der Adressraum ausgerichtet und die
subl $528, %espAnweisungen zur Verarbeitungsgeschwindigkeit der CPU beschleunigt , und öffnen Sie 528 Bytes im Stapelbereich. Der Compiler scannt im Voraus die Anzahl und Größe der lokalen Variablen in der Funktion und weist vorab einen Speicherplatz zum Speichern lokaler Variablen und Parameteraufrufe zu. Da im Folgenden ein int-Array mit einer Länge von 122 und anderen int-Variablen definiert wird, sind insgesamt 528 Byte Speicherplatz zum Speichern lokaler Variablen erforderlich.

movl    $0, 524(%esp)
movl    $0, 520(%esp)
movl    $1, 516(%esp)
movl    $2, 512(%esp)

Diese vier Sätze sollen jeweils vier Variablen a, b, op1, op2 definieren und diesen vier Variablen jeweils 0, 0, 1, 2 zuweisen

1. Assembly-Beschreibungsmethode der Verzweigungsanweisung

movl    516(%esp), %eax
cmpl    512(%esp), %eax
jne     .L9

Ordnen Sie op1 eax zu, dann vergleicht cmpl op1 und op2 in eax.
Jne-Anweisung ist nicht gleich zu springen. Wenn op1 nicht gleich op2 ist, dann springen Sie zu. L9
Wenn es gleich ist, führen Sie die folgende Anweisung aus

movl    $1, 524(%esp)
movl    $2, 520(%esp)

Weisen Sie a den Wert 1 und b den Wert 2 zu

2. Die Assembly-Beschreibungsmethode des Arrays.
Fahren Sie mit dem oben Gesagten fort. Wenn dies nicht der Fall ist, springen Sie zu .L9

movl    $66, 24(%esp)
movl    $666, 28(%esp)

Wenn Sie einem Array einen Wert zuweisen, wird ein kontinuierlicher Bereich mit der Größe (T)*N Bytes im Speicher zugewiesen.
Hier wird die Basisadresse des Arrays in ESP platziert. Weisen Sie zuerst arr [0] den Wert 66 zu, fügen Sie dann 4 von int zur Basisadresse hinzu, die arr [1] ist, und weisen Sie ihr den Wert 666 zu.

3. Die Assembly-Beschreibungsmethode des Zyklus
Nachdem der obige Prozess ausgeführt wurde, wird der Funktionsaufruf ausgeführt

call    printStar

ruft die printStar-Funktion
in der printStar-Funktion auf

printStar:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $40, %esp
        movl    $0, -12(%ebp)
        jmp     .L6
.L7:
        movl    $42, (%esp)
        call    putchar
        addl    $1, -12(%ebp)
.L6:
        cmpl    $9, -12(%ebp)
        jle     .L7
        leave
        ret

Die ersten beiden Sätze bestehen darin, den alten Rahmen zu speichern und einen neuen Stapelrahmen zu erstellen.
Die dritte Runde öffnet einen Raum von 40 Bytes, da sie als nächstes zehnmal wiederholt wird, zehn int i aufruft
und dann i einen Wert von 0 zuweist. JMP ist ein bedingungsloser Sprung. Springen Sie also direkt zu .L6
Cmpl, um i mit 9 zu vergleichen. Wenn weniger als 9, wird zu .L7 gesprungen

movl    $42, (%esp)
call    putchar
addl    $1, -12(%ebp)

Geben Sie dann 42 an esp an, 42 entspricht „ “

in ASCILL und rufen Sie dann putchar auf, um
addl $1, -12(%ebp) diesen Befehl auszugeben , indem Sie eins zu i hinzufügen

4. Wie wird der Assembler-Code des Funktionsaufrufs beschrieben und an welcher Adresse die Parameter platziert werden (es kann davon ausgegangen werden, dass die Adressen von %ebp und %esp bekannt sind), nachdem der obige Prozess ausgeführt
wurde

movl    $22, (%esp)
call    f

Schieben Sie 22 auf den Stapel (hier wird tatsächlich der als nächstes aufzurufende formale Parameter int x von f definiert und ein Wert zugewiesen), um f aufzurufen,
wobei der Wert von eip (PC-Zähler) zuerst auf dem Stapel gespeichert wird, der Wert von esp ist -4, und dann ist der Wert von f. Weisen Sie eip die Adresse zu
und geben Sie die f-Funktion ein.
Führen Sie zwei Anweisungen aus, um einen neuen Stapelrahmen zu erstellen und den neuen Funktionsaufrufstapel einzugeben.
Schieben Sie den Wert von ebp um 8 Byte (22) nach vorne der Stapel in main) zum Stapel,
hier esp-4 Es dient dazu, den formalen Parameter von g zu definieren und einen Wert zuzuweisen.
Rufen Sie g auf, eip wird zuerst auf den Stapel gelegt, und dann wird eip zur Adresse von g.
Geben Sie g ein Funktion:
Führen Sie zuerst die beiden Anweisungen aus, um einen neuen Stapelrahmen zu erstellen, geben Sie den neuen Aufrufstapel ein (wie oben) und
movl 8(%ebp), %eaxweisen Sie die erste Adresse zu. Weisen Sie die in f auf den Stapel geschobene Nummer (dh den formalen Parameter x von g) zu eax Der Wert in eax +99 entspricht hier
addl $99, %eaxder C-Anweisung x + 99, da in g kein neuer Stapelraum geöffnet ist (der Wert von esp hat sich nicht geändert), sodass dieser Satz der Leave-Anweisung entspricht . Nach der Ausführung dies Satz, ebp kehrt zum vorherigen Wert (dem Zustand in f) zurück und führt ret aus. Tatsächlich führt es popl eip aus und weist eip den im Stapel gespeicherten Wert von eip zu . Zu diesem Zeitpunkt kehren alle ebp, esp und eip zurück in den Zustand des Aufrufs von f. Verlassen Sie g und der Rückgabewert wird in eax gespeichert. Kehren Sie zur f-Funktion zurück: Führen Sie Leave und ret aus und stellen Sie die Werte von esp, ebp, eip wieder her. Dasselbe wie oben, f Gibt nur den Rückgabewert von g zurück, ändern Sie eax hier nicht, es speichert den Rückgabewert von g. Kehren Sie zur Hauptfunktion zurück: Der Rückgabewert von f(22) + 36, was f(22) + 36 in C entspricht Die Hauptfunktion „leave ret“ kehrt zur vorherigen Ebene zurück
popl %ebp








addl $36, %eax

Es sollte __libc_start_main in gcc sein, es kann den Rückgabewert von main in eax empfangen

Guess you like

Origin blog.csdn.net/L6666688888/article/details/128461077