Lenguaje ensamblador - Gestión de memoria

La gestión de memoria es una parte importante de la programación de sistemas. En lenguaje ensamblador, puedes asignar y liberar memoria dinámicamente mediante llamadas al sistema, manipular direcciones de memoria directamente y lograr una gestión de memoria eficiente.


Disposición de memoria de un programa

La distribución en memoria de un programa en ejecución es la siguiente:

程序内存布局图

El diseño en memoria de un proceso Linux es el siguiente:

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

Asignación dinámica de memoria: llamada al sistema brk

sys_brk(número de llamada al sistema 45) ajustando ellímite del segmento de datos (program break)para asignar/liberar memoria.

program break es la posición final del segmento de datos (incluye .data, .bss y el montón); la memoria por encima de esta posición no puede ser accedida por el programa.

Forma de llamadaDescripciónValor de retorno
ebx = 0Obtener la dirección break actualEAX = dirección break actual
ebx = 新地址Establecer una nueva dirección breakEAX = nueva dirección break (si falla, devuelve la dirección original)

Ejemplo

; Ruta del archivo: brk_demo.asm
; Usar sys_brk para asignar memoria dinámicamente

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

section .bss
    orig_break resd 1            ; Guardar la dirección break original

section .text
    global _start

_start:
    ; 1. Obtener el program break actual
    mov eax, 45                  ; sys_brk
    mov ebx, 0                   ; ebx=0 indica consultar el break actual
    int 0x80
    mov [orig_break], eax        ; Guardar la dirección break original

    ; 2. Asignar 4096 bytes (4KB) de memoria
    mov ebx, eax                 ; Dirección break actual
    add ebx, 4096                ; Aumentar 4096 bytes
    mov eax, 45                  ; sys_brk
    int 0x80
    ; EAX = nueva dirección break (es decir, el final del área asignada)

    ; 3. La memoria recién asignada está entre orig_break y eax-1
    ; Se puede leer y escribir esta memoria de forma segura
    mov ebx, [orig_break]        ; Obtener la dirección inicial del área asignada
    mov dword [ebx], 42          ; Escribir 42 en la nueva memoria
    mov eax, [ebx]               ; Leer de vuelta

    ; 4. Liberar memoria: restaurar el break a su posición original
    mov eax, 45                  ; sys_brk
    mov ebx, [orig_break]        ; Restaurar al break original
    int 0x80

    mov eax, 1
    mov ebx, 0
    int 0x80

sys_brk solo puede ajustar el límite contiguo del segmento de datos; no puede liberar memoria en medio. Para liberar un bloque de memoria ya asignado, primero debes liberar toda la memoria asignada posteriormente. Por eso los programas modernos usan más a menudo mmap o la función de biblioteca malloc.


Asignar memoria con mmap (recomendado)

sys_mmap2(número de llamada al sistema 192) es más flexible: puede asignar y liberar múltiples bloques de memoria de forma independiente:

Ejemplo

; Ruta del archivo: mmap_demo.asm
; Usar mmap para asignar memoria que se puede liberar de forma independiente

section .data
    ; Constantes relacionadas con mmap
    PROT_READ equ 1
    PROT_WRITE equ 2
    MAP_PRIVATE equ 2
    MAP_ANONYMOUS equ 0x20

section .bss
    mem_block resd 1             ; Guardar la dirección de memoria asignada

section .text
    global _start

_start:
    ; Llamada a mmap: asignar 4096 bytes de memoria anónima
    mov eax, 192                 ; sys_mmap2
    mov ebx, 0                   ; Dejar que el kernel elija la dirección
    mov ecx, 4096                ; Tamaño de asignación: 4KB
    mov edx, PROT_READ | PROT_WRITE  ; Legible y escribible
    mov esi, MAP_PRIVATE | MAP_ANONYMOUS  ; Mapeo anónimo privado
    mov edi, -1                  ; Descriptor de archivo (-1 para mapeo anónimo)
    mov ebp, 0                   ; Desplazamiento (0 para mapeo anónimo)
    int 0x80
    ; EAX = dirección de memoria asignada (devuelve un valor negativo si falla)

    cmp eax, -4096               ; Comprobar si falló
    ja  mmap_failed              ; Si está entre -4095 y -1, falló

    mov [mem_block], eax         ; Guardar la dirección de memoria

    ; Usar la memoria asignada: escribir datos
    mov ebx, [mem_block]
    mov dword [ebx], 0x12345678  ; Escribir 4 bytes
    mov dword [ebx + 4], 'runo'  ; Escribir \"runo\"
    mov dword [ebx + 8], 'ob!!'  ; Escribir \"ob!!\"

    ; Liberar memoria: munmap
    mov eax, 91                  ; sys_munmap
    mov ebx, [mem_block]         ; Dirección de memoria
    mov ecx, 4096                ; Tamaño a liberar
    int 0x80

    jmp exit

mmap_failed:
    ; Manejar el error...

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

Operaciones de lectura y escritura de memoria

En lenguaje ensamblador, todo acceso a memoria se realiza mediante lamovinstrucción combinada con corchetes:

Ejemplo

; Operaciones básicas de lectura y escritura de memoria

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

section .text
    global _start

_start:
    ; Leer memoria de diferentes tamaños
    mov al, [var1]              ; Leer 1 byte: al = 0x55
    mov ax, [var2]              ; Leer 2 bytes: ax = 0x1234
    mov eax, [var3]             ; Leer 4 bytes: eax = 0x12345678

    ; Escribir memoria de diferentes tamaños
    mov byte [var1], 0xAA       ; Escribir 1 byte
    mov word [var2], 0xABCD     ; Escribir 2 bytes
    mov dword [var3], 0xDEADBEEF ; Escribir 4 bytes

    ; Operar la memoria mediante punteros
    mov ebx, var3               ; ebx apunta a var3
    mov eax, [ebx]              ; Leer indirectamente el valor de var3
    add dword [ebx], 1          ; var3 = var3 + 1

    mov eax, 1
    mov ebx, 0
    int 0x80

Reglas de seguridad para operaciones de memoria

En ensamblador, manipular memoria no tiene \"red de seguridad\"; debes tener presentes las siguientes reglas:

ReglaDescripciónConsecuencia de violarla
No acceder fuera de los límitesLeer o escribir sin superar el tamaño de la variable / búferCorrupción de datos, error de segmentación
No leer memoria no inicializadaLos valores de la sección .bss son indefinidosResultados impredecibles
No leer ni escribir en direcciones inválidasAsegurarse de que el puntero apunte a memoria válidaError de segmentación (Segmentation Fault)
No escribir en memoria de solo lecturaLa sección .text y el área de constantes no son escribiblesError de segmentación
Alineación de direccionesAcceso a palabras con direcciones pares, dobles palabras con múltiplos de 4Degradación del rendimiento o error de bus

Ejemplo

; Ejemplo de errores comunes de memoria (¡advertencia: puede provocar un fallo!)

section .data
    small_buf db 0, 0, 0, 0     ; Solo 4 bytes

section .text
    global _start

_start:
    ; Operación peligrosa 1: escritura fuera de límites
    ; mov dword [small_buf + 3], 0x12345678
    ; ¡Esto sobrescribirá 3 bytes de datos después de small_buf!

    ; Operación peligrosa 2: escribir en la sección de código (solo lectura)
    ; mov dword [_start], 0x90 ; intentar modificar el código provocará un error de segmentación

    ; Operación peligrosa 3: puntero nulo / dirección inválida
    ; mov eax,
    ; mov

    ; Práctica segura: operar siempre dentro del rango de memoria asignado
    mov dword [small_buf], 'runo'  ; Correcto: operar dentro de los 4 bytes

    mov eax, 1
    mov ebx, 0
    int 0x80

Gestión de la memoria de pila

La pila es otra región de memoria importante, gestionada por las instrucciones PUSH/POP y el registro ESP:

Ejemplo

; Ejemplo completo de operaciones de pila

section .data
    original_esp dd 0

section .text
    global _start

_start:
    mov [original_esp], esp      ; Guardar el puntero de pila original

    ; PUSH: empujar a la pila (ESP se reduce en 4, se escriben los datos)
    push dword 100               ; Empujar 100
    ; ESP = ESP - 4, [ESP] = 100

    push dword 200               ; Empujar 200
    push dword 300               ; Empujar 300

    ; Diseño de la pila:
    ; ESP+0: 300
    ; ESP+4: 200
    ; ESP+8: 100

    ; POP: extraer de la pila (leer datos, ESP aumenta en 4)
    pop eax                      ; eax = 300, ESP += 4
    pop ebx                      ; ebx = 200, ESP += 4
    pop ecx                      ; ecx = 100, ESP += 4

    ; Asignar espacio local mediante la pila
    sub esp, 256                 ; Asignar 256 bytes en la pila
    ; Ahora los 256 bytes desde ESP hasta ESP+255 se pueden usar de forma segura
    mov dword [esp], 42          ; Escribir 42 en el espacio local
    add esp, 256                 ; Liberar el espacio local

    ; ¡Asegúrate de que ESP vuelva a su posición original!
    mov eax, 1
    mov ebx, 0
    int 0x80

La regla más importante de las operaciones de pila:PUSH y POP deben estar emparejados. Antes de que la función retorne, debes asegurarte de que ESP vuelva a la posición correcta; de lo contrario, RET extraerá una dirección de retorno incorrecta, lo que provocará un fallo del programa o la ejecución de código arbitrario. Este es uno de los errores más mortales en programación en ensamblador.


Comparación de esquemas de gestión de memoria

EsquemaFlexibilidadComplejidadEscenario de aplicación
Variables globales (.data/.bss)Baja (fijas en tiempo de compilación)La más bajaDatos de tamaño fijo
Asignación en pila (sub esp)medio (dinámico dentro de la función)bajoVariables locales de función
brk/sbrkmedio (solo puede crecer/encogerse)MedioRegión de memoria contigua
mmapalto (asignación y liberación arbitrarias)AltoCuando se necesita una gestión flexible de la memoria
Otras extensiones