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 llamada | Descripción | Valor de retorno |
|---|---|---|
ebx = 0 | Obtener la dirección break actual | EAX = dirección break actual |
ebx = 新地址 | Establecer una nueva dirección break | EAX = nueva dirección break (si falla, devuelve la dirección original) |
Ejemplo
; 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
; 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
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:
| Regla | Descripción | Consecuencia de violarla |
|---|---|---|
| No acceder fuera de los límites | Leer o escribir sin superar el tamaño de la variable / búfer | Corrupción de datos, error de segmentación |
| No leer memoria no inicializada | Los valores de la sección .bss son indefinidos | Resultados impredecibles |
| No leer ni escribir en direcciones inválidas | Asegurarse de que el puntero apunte a memoria válida | Error de segmentación (Segmentation Fault) |
| No escribir en memoria de solo lectura | La sección .text y el área de constantes no son escribibles | Error de segmentación |
| Alineación de direcciones | Acceso a palabras con direcciones pares, dobles palabras con múltiplos de 4 | Degradación del rendimiento o error de bus |
Ejemplo
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
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
| Esquema | Flexibilidad | Complejidad | Escenario de aplicación |
|---|---|---|---|
| Variables globales (.data/.bss) | Baja (fijas en tiempo de compilación) | La más baja | Datos de tamaño fijo |
| Asignación en pila (sub esp) | medio (dinámico dentro de la función) | bajo | Variables locales de función |
| brk/sbrk | medio (solo puede crecer/encogerse) | Medio | Región de memoria contigua |
| mmap | alto (asignación y liberación arbitrarias) | Alto | Cuando se necesita una gestión flexible de la memoria |