Assemblersprache - Speicherverwaltung

Die Speicherverwaltung ist ein wichtiger Teil der Systemprogrammierung. In Assemblersprache können Sie Speicher dynamisch über Systemaufrufe zuweisen und freigeben, Speicheradressen direkt manipulieren und eine effiziente Speicherverwaltung erreichen.


Speicherlayout eines Programms

Ein laufendes Programm ist im Speicher wie folgt verteilt:

程序内存布局图

Das Speicherlayout eines Linux-Prozesses sieht wie folgt aus:

高地址 (0xFFFFFFFF)
+----------------------+
|    内核空间           |  用户程序不可访问
+----------------------+  (0xC0000000)
|    栈 (Stack)        |  <-- ESP 指向栈顶
|    向下增长           |
+----------------------+
|                      |
|    内存映射区域        |  mmap 分配的区域
|                      |
+----------------------+
|    堆 (Heap)         |  <-- brk/sbrk 管理
|    向上增长           |
+----------------------+
|    BSS 段 (.bss)     |  未初始化全局变量
+----------------------+
|    数据段 (.data)     |  已初始化全局变量
+----------------------+
|    代码段 (.text)     |  程序指令(只读)
+----------------------+
低地址 (0x08048000)

Dynamische Speicherzuweisung: brk-Systemaufruf

sys_brk(Systemaufrufnummer 45) durch Anpassen derDatensegmentgrenze des Programms (Program Break)zum Zuweisen/Freigeben von Speicher.

Der Program Break ist die Endposition des Datensegments (einschließlich .data, .bss und des Heaps). Das Programm kann nicht auf Speicher darüber zugreifen.

AufrufmethodeBeschreibungRückgabewert
ebx = 0Aktuelle Break-Adresse abrufenEAX = aktuelle Break-Adresse
ebx = 新地址Neue Break-Adresse festlegenEAX = neue Break-Adresse (gibt bei Fehlschlag die ursprüngliche Adresse zurück)

Beispiel

; Dateipfad: brk_demo.asm
; Verwenden Sie sys_brk, um Speicher dynamisch zuzuweisen

section .data
    alloc_msg db 'Memory allocated successfully at: 0x'
    alloc_len equ $ - alloc_msg
    newline db 0xA

section .bss
    orig_break resd 1            ; Speichern der ursprünglichen Break-Adresse

section .text
    global _start

_start:
    ; 1. Aktuellen Program Break abrufen
    mov eax, 45                  ; sys_brk
    mov ebx, 0                   ; ebx=0 bedeutet aktuellen Break abfragen
    int 0x80
    mov [orig_break], eax        ; Ursprüngliche Break-Adresse speichern

    ; 2. 4096 Bytes (4 KB) Speicher zuweisen
    mov ebx, eax                 ; Aktuelle Break-Adresse
    add ebx, 4096                ; Um 4096 Bytes erhöhen
    mov eax, 45                  ; sys_brk
    int 0x80
    ; EAX = neue Break-Adresse (d. h. das Ende des zugewiesenen Bereichs)

    ; 3. Der neu zugewiesene Speicher liegt zwischen orig_break und eax-1
    ; Sie können diesen Speicherbereich sicher lesen und schreiben
    mov ebx, [orig_break]        ; Startadresse des zugewiesenen Bereichs abrufen
    mov dword [ebx], 42          ; 42 in den neuen Speicher schreiben
    mov eax, [ebx]               ; Zurücklesen

    ; 4. Speicher freigeben: Break auf die ursprüngliche Position zurücksetzen
    mov eax, 45                  ; sys_brk
    mov ebx, [orig_break]        ; Auf den ursprünglichen Break zurücksetzen
    int 0x80

    mov eax, 1
    mov ebx, 0
    int 0x80

sys_brk kann nur die Grenze des kontinuierlichen Datensegments anpassen und keinen Speicher in der Mitte freigeben. Um einen zugewiesenen Speicherblock freizugeben, müssen Sie zuerst allen danach zugewiesenen Speicher freigeben. Aus diesem Grund verwenden moderne Programme häufiger mmap oder die malloc-Bibliotheksfunktionen.


Verwendung von mmap zum Zuweisen von Speicher (Empfohlen)

sys_mmap2(Systemaufrufnummer 192) ist flexibler; er kann mehrere Speicherblöcke unabhängig voneinander zuweisen und freigeben:

Beispiel

; Dateipfad: mmap_demo.asm
; mmap verwenden, um unabhängig freigebbaren Speicher zuzuweisen

section .data
    ; mmap-bezogene Konstanten
    PROT_READ equ 1
    PROT_WRITE equ 2
    MAP_PRIVATE equ 2
    MAP_ANONYMOUS equ 0x20

section .bss
    mem_block resd 1             ; Zugewiesene Speicheradresse speichern

section .text
    global _start

_start:
    ; mmap-Aufruf: 4096 Bytes anonymen Speicher zuweisen
    mov eax, 192                 ; sys_mmap2
    mov ebx, 0                   ; Kernel die Adresse auswählen lassen
    mov ecx, 4096                ; Zuweisungsgröße: 4 KB
    mov edx, PROT_READ | PROT_WRITE  ; Lesbar und schreibbar
    mov esi, MAP_PRIVATE | MAP_ANONYMOUS  ; Private anonyme Zuordnung
    mov edi, -1                  ; Dateideskriptor (-1 für anonyme Zuordnung verwenden)
    mov ebp, 0                   ; Versatz (0 für anonyme Zuordnung verwenden)
    int 0x80
    ; EAX = zugewiesene Speicheradresse (gibt bei Fehlschlag einen negativen Wert zurück)

    cmp eax, -4096               ; Prüfen, ob es fehlgeschlagen ist
    ja  mmap_failed              ; Wenn es zwischen -4095 und -1 liegt, ist es fehlgeschlagen

    mov [mem_block], eax         ; Speicheradresse speichern

    ; Zugewiesenen Speicher verwenden: Daten schreiben
    mov ebx, [mem_block]
    mov dword [ebx], 0x12345678  ; 4 Bytes schreiben
    mov dword [ebx + 4], 'runo'  ; "runo" schreiben
    mov dword [ebx + 8], 'ob!!'  ; "ob!!" schreiben

    ; Speicher freigeben: munmap
    mov eax, 91                  ; sys_munmap
    mov ebx, [mem_block]         ; Speicheradresse
    mov ecx, 4096                ; Freigabegröße
    int 0x80

    jmp exit

mmap_failed:
    ; Fehler behandeln...

exit:
    mov eax, 1
    mov ebx, 0
    int 0x80

Lese- und Schreiboperationen im Speicher

In Assemblersprache erfolgen alle Speicherzugriffe übermovBefehle in Kombination mit eckigen Klammern:

Beispiel

; Grundlegende Lese- und Schreiboperationen im Speicher

section .data
    var1 db 0x55                ; 1 Byte
    var2 dw 0x1234              ; 2 Bytes
    var3 dd 0x12345678          ; 4 Bytes

section .text
    global _start

_start:
    ; Speicher verschiedener Größen lesen
    mov al, [var1]              ; 1 Byte lesen: al = 0x55
    mov ax, [var2]              ; 2 Bytes lesen: ax = 0x1234
    mov eax, [var3]             ; 4 Bytes lesen: eax = 0x12345678

    ; Schreiben von Speicher unterschiedlicher Größe
    mov byte [var1], 0xAA       ; 1 Byte schreiben
    mov word [var2], 0xABCD     ; 2 Bytes schreiben
    mov dword [var3], 0xDEADBEEF ; 4 Bytes schreiben

    ; Zugriff auf Speicher über Zeiger
    mov ebx, var3               ; ebx zeigt auf var3
    mov eax, [ebx]              ; Wert von var3 indirekt lesen
    add dword [ebx], 1          ; var3 = var3 + 1

    mov eax, 1
    mov ebx, 0
    int 0x80

Sicherheitsrichtlinien für Speicheroperationen

Bei Speicheroperationen in Assembler gibt es kein "Sicherheitsnetz"; beachten Sie die folgenden Regeln:

RegelBeschreibungKonsequenz des Verstoßes
Kein Zugriff außerhalb der GrenzenNicht mehr als die Größe der Variable/des Puffers lesen/schreibenDatenbeschädigung, Speicherzugriffsfehler (Segmentation Fault)
Keinen uninitialisierten Speicher lesenWerte im .bss-Abschnitt sind undefiniertUnvorhersehbare Ergebnisse
Nicht an ungültigen Adressen lesen/schreibenSicherstellen, dass der Zeiger auf gültigen Speicher zeigtSpeicherzugriffsfehler (Segmentation Fault)
Nicht in schreibgeschützten Speicher schreibenDer .text-Abschnitt und der Konstantenbereich sind nicht beschreibbarSpeicherzugriffsfehler (Segmentation Fault)
AdresstypausrichtungWortzugriffe verwenden gerade Adressen; Doppelwortzugriffe verwenden Vielfache von 4Leistungseinbußen oder Busfehler

Beispiel

; Beispiele für häufige Speicherfehler (Warnung: führt zu Abstürzen!)

section .data
    small_buf db 0, 0, 0, 0     ; Nur 4 Bytes

section .text
    global _start

_start:
    ; Gefährliche Operation 1: Schreiben außerhalb der Grenzen
    ; mov dword [small_buf + 3], 0x12345678
    ; Dies überschreibt 3 Bytes an Daten nach small_buf!

    ; Gefährliche Operation 2: Schreiben in den Codeabschnitt (schreibgeschützt)
    ; mov dword [_start], 0x90 ; Der Versuch, Code zu modifizieren, verursacht einen Speicherzugriffsfehler

    ; Gefährliche Operation 3: Nullzeiger / ungültige Adresse
    ; mov eax,
    ; mov

    ; Sicherer Ansatz: immer innerhalb des zugewiesenen Speicherbereichs arbeiten
    mov dword [small_buf], 'runo'  ; Korrekt: innerhalb von 4 Bytes arbeiten

    mov eax, 1
    mov ebx, 0
    int 0x80

Stapel-Speicherverwaltung (Stack)

Der Stapel (Stack) ist ein weiterer wichtiger Speicherbereich, der über die PUSH/POP-Anweisungen und das ESP-Register verwaltet wird:

Beispiel

; Umfassendes Beispiel für Stapeloperationen

section .data
    original_esp dd 0

section .text
    global _start

_start:
    mov [original_esp], esp      ; Den ursprünglichen Stapelzeiger speichern

    ; PUSH: auf den Stapel legen (ESP -= 4, Daten schreiben)
    push dword 100               ; 100 auf den Stapel legen
    ; ESP = ESP - 4, [ESP] = 100

    push dword 200               ; 200 auf den Stapel legen
    push dword 300               ; 300 auf den Stapel legen

    ; Stapel-Layout:
    ; ESP+0: 300
    ; ESP+4: 200
    ; ESP+8: 100

    ; POP: vom Stapel nehmen (Daten lesen, ESP += 4)
    pop eax                      ; eax = 300, ESP += 4
    pop ebx                      ; ebx = 200, ESP += 4
    pop ecx                      ; ecx = 100, ESP += 4

    ; Lokalen Speicher auf dem Stapel zuweisen
    sub esp, 256                 ; 256 Bytes auf dem Stapel zuweisen
    ; Nun können die 256 Bytes von ESP bis ESP+255 sicher verwendet werden
    mov dword [esp], 42          ; 42 in den lokalen Speicher schreiben
    add esp, 256                 ; Lokalen Speicher freigeben

    ; Sicherstellen, dass ESP zu seiner ursprünglichen Position zurückkehrt!
    mov eax, 1
    mov ebx, 0
    int 0x80

Die wichtigste Regel für Stapeloperationen:PUSH und POP müssen paarweise verwendet werden. Bevor eine Funktion zurückkehrt, müssen Sie sicherstellen, dass ESP zur korrekten Position zurückkehrt; andernfalls holt RET die falsche Rückspraddresse vom Stapel, was zum Absturz des Programms oder zur Ausführung von beliebigem Code führt. Dies ist einer der fatalsten Bugs in der Assembler-Programmierung.


Vergleich von Speicherverwaltungsmethoden

SchemaFlexibilitätKomplexitätAnwendungsszenario
Globale Variablen (.data/.bss)Gering (zur Kompilierzeit festgelegt)Am niedrigstenDaten mit fester Größe
Stapel-Zuweisung (sub esp)Mittel (dynamisch innerhalb der Funktion)NiedrigLokale Variablen von Funktionen
brk/sbrkMittel (kann nur wachsen/schrumpfen)MittelZusammenhängender Speicherbereich
mmapHoch (beliebige Zuweisung/Freigabe)HochWenn eine flexible Speicherverwaltung erforderlich ist
Weitere Erweiterungen