Язык ассемблера - Управление памятью
Управление памятью — важная часть системного программирования. В языке ассемблера можно динамически выделять и освобождать память с помощью системных вызовов, напрямую управлять адресами памяти и обеспечивать эффективное управление памятью.
Схема распределения памяти программы
Запущенная программа распределена в памяти следующим образом:
Схема распределения памяти процесса Linux выглядит следующим образом:
高地址 (0xFFFFFFFF) +----------------------+ | 内核空间 | 用户程序不可访问 +----------------------+ (0xC0000000) | 栈 (Stack) | <-- ESP 指向栈顶 | 向下增长 | +----------------------+ | | | 内存映射区域 | mmap 分配的区域 | | +----------------------+ | 堆 (Heap) | <-- brk/sbrk 管理 | 向上增长 | +----------------------+ | BSS 段 (.bss) | 未初始化全局变量 +----------------------+ | 数据段 (.data) | 已初始化全局变量 +----------------------+ | 代码段 (.text) | 程序指令(只读) +----------------------+ 低地址 (0x08048000)
Динамическое выделение памяти: системный вызов brk
sys_brk(Системный вызов номер 45) путём измененияграницы сегмента данных программы (program break)для выделения/освобождения памяти.
program break — это конечная позиция сегмента данных (включая .data, .bss и кучу). Программа не может обращаться к памяти выше неё.
| Способ вызова | Описание | Возвращаемое значение |
|---|---|---|
ebx = 0 | Получить текущий адрес break | EAX = текущий адрес границы |
ebx = 新地址 | Установить новый адрес границы | EAX = новый адрес границы (при ошибке возвращает исходный адрес) |
Пример
; Использовать sys_brk для динамического выделения памяти
section .data
alloc_msg db 'Память успешно выделена по адресу: 0x'
alloc_len equ $ - alloc_msg
newline db 0xA
section .bss
orig_break resd 1 ; Сохранить исходный адрес границы
section .text
global _start
_start:
; 1. Получить текущую границу программы
mov eax, 45 ; sys_brk
mov ebx, 0 ; ebx=0 означает запросить текущую границу
int 0x80
mov [orig_break], eax ; Сохранить исходный адрес границы
; 2. Выделить 4096 байт (4KB) памяти
mov ebx, eax ; Текущий адрес границы
add ebx, 4096 ; Увеличить на 4096 байт
mov eax, 45 ; sys_brk
int 0x80
; EAX = новый адрес границы (т.е. конец выделенной области)
; 3. Новый выделенный участок памяти находится между orig_break и eax-1
; Вы можете безопасно читать и записывать в эту область памяти
mov ebx, [orig_break] ; Получить начальный адрес выделенной области
mov dword [ebx], 42 ; Записать 42 в новую память
mov eax, [ebx] ; Прочитать обратно
; 4. Освободить память: вернуть границу в исходное положение
mov eax, 45 ; sys_brk
mov ebx, [orig_break] ; Восстановить исходную границу
int 0x80
mov eax, 1
mov ebx, 0
int 0x80
sys_brk может только изменять границу непрерывного сегмента данных и не может освобождать память в середине. Чтобы освободить блок выделенной памяти, необходимо сначала освободить всю память, выделенную после него. Именно поэтому современные программы чаще используют mmap или библиотечные функции malloc.
Использование mmap для выделения памяти (рекомендуется)
sys_mmap2(Системный вызов номер 192) более гибкий; он может независимо выделять и освобождать несколько блоков памяти:
Пример
; Использование mmap для выделения памяти, которую можно освобождать независимо
section .data
; константы, связанные с mmap
PROT_READ equ 1
PROT_WRITE equ 2
MAP_PRIVATE equ 2
MAP_ANONYMOUS equ 0x20
section .bss
mem_block resd 1 ; Сохранить адрес выделенной памяти
section .text
global _start
_start:
; вызов mmap: выделить 4096 байт анонимной памяти
mov eax, 192 ; sys_mmap2
mov ebx, 0 ; Пусть ядро выберет адрес
mov ecx, 4096 ; Размер выделения: 4KB
mov edx, PROT_READ | PROT_WRITE ; Доступно для чтения и записи
mov esi, MAP_PRIVATE | MAP_ANONYMOUS ; Приватное анонимное отображение
mov edi, -1 ; Файловый дескриптор (используйте -1 для анонимного отображения)
mov ebp, 0 ; Смещение (используйте 0 для анонимного отображения)
int 0x80
; EAX = адрес выделенной памяти (возвращает отрицательное значение при ошибке)
cmp eax, -4096 ; Проверить, не произошла ли ошибка
ja mmap_failed ; Если значение между -4095 и -1, значит произошла ошибка
mov [mem_block], eax ; Сохранить адрес памяти
; Использовать выделенную память: записать данные
mov ebx, [mem_block]
mov dword [ebx], 0x12345678 ; Записать 4 байта
mov dword [ebx + 4], 'runo' ; Записать "runo"
mov dword [ebx + 8], 'ob!!' ; Записать "ob!!"
; Освободить память: munmap
mov eax, 91 ; sys_munmap
mov ebx, [mem_block] ; Адрес памяти
mov ecx, 4096 ; Размер освобождения
int 0x80
jmp exit
mmap_failed:
; Обработать ошибки...
exit:
mov eax, 1
mov ebx, 0
int 0x80
Операции чтения/записи памяти
В языке ассемблера все обращения к памяти выполняются черезmovкоманды в сочетании с квадратными скобками:
Пример
section .data
var1 db 0x55 ; 1 byte
var2 dw 0x1234 ; 2 bytes
var3 dd 0x12345678 ; 4 bytes
section .text
global _start
_start:
; Чтение памяти разных размеров
mov al, [var1] ; Чтение 1 byte: al = 0x55
mov ax, [var2] ; Чтение 2 bytes: ax = 0x1234
mov eax, [var3] ; Чтение 4 bytes: eax = 0x12345678
; Запись в память разных размеров
mov byte [var1], 0xAA ; Записать 1 байт
mov word [var2], 0xABCD ; Записать 2 байта
mov dword [var3], 0xDEADBEEF ; Записать 4 байта
; Доступ к памяти через указатели
mov ebx, var3 ; ebx указывает на var3
mov eax, [ebx] ; Косвенно прочитать значение var3
add dword [ebx], 1 ; var3 = var3 + 1
mov eax, 1
mov ebx, 0
int 0x80
Правила безопасности при операциях с памятью
При работе с памятью в ассемблере нет "страховки"; помните о следующих правилах:
| Правило | Описание | Последствие нарушения |
|---|---|---|
| Не допускайте доступа за границы | Чтение/запись не более размера переменной/буфера | Повреждение данных, ошибка сегментации |
| Не читайте неинициализированную память | Значения в секции .bss не определены | Непредсказуемые результаты |
| Не читайте/записывайте по недопустимым адресам | Убедитесь, что указатель указывает на допустимую память | Ошибка сегментации |
| Не записывайте в память только для чтения | Секция .text и область констант не доступны для записи | Ошибка сегментации |
| Выравнивание адреса | Обращения к слову используют чётные адреса; обращения к двойному слову используют кратные 4 | Снижение производительности или ошибка шины |
Пример
section .data
small_buf db 0, 0, 0, 0 ; Только 4 байта
section .text
global _start
_start:
; Опасная операция 1: запись за границы
; mov dword [small_buf + 3], 0x12345678
; Это перезаписывает 3 байта данных после small_buf!
; Опасная операция 2: запись в секцию кода (только для чтения)
; mov dword [_start], 0x90 ; Попытка изменить код приведёт к ошибке сегментации
; Опасная операция 3: нулевой указатель / недопустимый адрес
; mov eax,
; mov
; Безопасный подход: всегда работать в пределах выделенного диапазона памяти
mov dword [small_buf], 'runo' ; Корректно: работать в пределах 4 bytes
mov eax, 1
mov ebx, 0
int 0x80
Управление памятью стека
Стек — это ещё одна важная область памяти, управляемая инструкциями PUSH/POP и регистром ESP:
Пример
section .data
original_esp dd 0
section .text
global _start
_start:
mov [original_esp], esp ; Сохранить исходный указатель стека
; PUSH: поместить в стек (ESP -= 4, записать данные)
push dword 100 ; Поместить 100
; ESP = ESP - 4, [ESP] = 100
push dword 200 ; Поместить 200
push dword 300 ; Поместить 300 в стек
; Структура стека:
; ESP+0: 300
; ESP+4: 200
; ESP+8: 100
; POP: извлечь из стека (прочитать данные, ESP += 4)
pop eax ; eax = 300, ESP += 4
pop ebx ; ebx = 200, ESP += 4
pop ecx ; ecx = 100, ESP += 4
; Выделить локальное пространство в стеке
sub esp, 256 ; Выделить 256 байт в стеке
; Теперь 256 байт от ESP до ESP+255 можно безопасно использовать
mov dword [esp], 42 ; Записать 42 в локальную область
add esp, 256 ; Освободить локальную область
; Убедитесь, что ESP возвращается в исходное положение!
mov eax, 1
mov ebx, 0
int 0x80
Самое важное правило для операций со стеком:PUSH и POP должны быть парными. Перед возвратом из функции вы должны убедиться, что ESP возвращается в правильное положение; иначе RET извлечёт неверный адрес возврата, что приведёт к падению программы или выполнению произвольного кода. Это одна из самых фатальных ошибок в программировании на ассемблере.
Сравнение методов управления памятью
| Схема | Гибкость | Сложность | Сценарий применения |
|---|---|---|---|
| Глобальные переменные (.data/.bss) | Низкая (фиксировано во время компиляции) | Самый низкий | Данные фиксированного размера |
| Выделение в стеке (sub esp) | Средняя (динамически внутри функции) | Низкая | Локальные переменные функции |
| brk/sbrk | Средний (может только расти/сжиматься) | Средняя | Непрерывная область памяти |
| mmap | Высокий (произвольное выделение/освобождение) | Высокая | Когда требуется гибкое управление памятью |