x64 Linux 汇编: 可变长字符数组的动态处理
2025-02-04 01:01:37
x64 Linux 汇编中可变长度字符数组的处理
在 x64 Linux 汇编编程中,处理可变长度字符数组是一个常见的问题。固定大小的缓冲区往往不够灵活,而.bss
段的大量声明则显得笨拙。如何有效地分配和管理字符数组,同时避免常见的陷阱呢? 本文将探讨几种可能的解决方案。
问题根源
汇编语言不像高级语言那样提供内置的字符串类型和自动内存管理。因此,动态创建和调整字符数组的存储空间需要手动完成。 常规做法使用预先分配的固定大小缓冲区(.bss
段),但这限制了字符串的长度,并且当需要连接字符串时,需要精心管理缓冲区的大小和内存复制操作。 使用栈空间尝试动态扩展面临的问题是如何有效地回收空间,且在多个函数调用之间难以维持。
解决方案:利用 mmap
进行动态内存分配
mmap
系统调用允许从操作系统内核分配一块虚拟内存区域。这种方法具有高度的灵活性,可以动态地分配所需大小的内存。与在栈上分配内存不同,mmap
分配的内存位于堆上,生命周期更长,更适合存储需要跨函数使用的字符串。
实现步骤:
-
使用
mmap
系统调用分配内存。你需要指定MAP_ANONYMOUS
和MAP_PRIVATE
标志以获取一块私有匿名映射。 还需要提供所需内存的大小。 -
将分配的内存地址保存到寄存器或内存位置,以便后续使用。
-
进行字符串操作(复制,连接等),确保不超过已分配的内存大小。必要时重新分配更大的内存块。
-
完成操作后,使用
munmap
系统调用释放内存。
代码示例:
; Syscall numbers
%define SYS_MMAP 9
%define SYS_MUNMAP 11
%define SYS_EXIT 60
; mmap flags
%define MAP_ANONYMOUS 0x20
%define MAP_PRIVATE 0x02
;Prot flags
%define PROT_READ 0x1
%define PROT_WRITE 0x2
section .data
message db "Hello, world!", 0 ;example string
section .text
global _start
_start:
; Allocate memory using mmap
mov rax, SYS_MMAP ; sys_mmap
mov rdi, 0 ; addr (NULL for kernel to choose)
mov rsi, 4096 ; length (page size - common practice)
mov rdx, PROT_READ | PROT_WRITE ; prot (read/write)
mov r10, MAP_ANONYMOUS | MAP_PRIVATE ; flags (anonymous private mapping)
mov r8, -1 ; fd (-1 for anonymous mapping)
mov r9, 0 ; offset (0)
syscall
; Check for errors (rax contains the pointer to the new allocation on success or -errno on failure)
cmp rax, 0 ;error value
jl error_handler ; handle error. Exit program as exemple
; Save the allocated memory address
mov r15, rax ; Store allocated memory address in r15
;Copy string into the mmap'ed section of memory. (replace rsi and rdi value with data section and mmap alloc respectively)
mov rsi, message
mov rdi, r15
call string_copy ;Simple loop to copy the string from a source to a dest buffer
;Print the copied string
mov rsi, r15 ; rsi should point to our buffer in memory
call .print ; print routine (Given code)
;Deallocate the memory
mov rax, SYS_MUNMAP
mov rdi, r15 ; address obtained with mmap call
mov rsi, 4096; length (same length that we asked to mmap).
syscall
; Exit the program
mov rax, SYS_EXIT
xor rdi, rdi
syscall
;----------------------------;
string_copy:
push rbp
mov rbp, rsp
.copy_loop:
mov al, [rsi]
cmp al, 0
je .copy_end
mov [rdi], al
inc rsi
inc rdi
jmp .copy_loop
.copy_end:
pop rbp
ret
;----- Print Routine (same than give code) ----------
.print:
push rcx ; save increment if used in another function
xor rcx, rcx ; reset increment to 0
.print_loop:
cmp byte [rsi + rcx], 0 ; check if null
je .print_done ; if yes print the value beacause end of string
inc rcx ; else increment
jmp .print_loop ; and restart the operation
.print_done:
mov rdx, rcx ; pass the string size as parameter syscall sys_write
mov rax, 1 ; prepare syscall sys_write function
mov rdi, 1 ; prepare syscall stdout output
syscall ; print the string
pop rcx ; get the old increment value to handle return
ret
;-------------ERROR HANDLER----------
error_handler:
;Basic error handling logic. Print error message (if applicable) and terminate execution
mov rax, SYS_EXIT
mov rdi, 1
syscall
安全建议:
- 始终检查
mmap
的返回值,以确保内存分配成功。 - 确保在使用完
mmap
分配的内存后,使用munmap
及时释放内存,避免内存泄漏。 - 对写入
mmap
区域的数据量进行限制,以防止缓冲区溢出。可以使用realloc
的思路进行内存扩展,需要复制原有数据。
brk
系统调用进行内存管理
brk
和 sbrk
系统调用直接操纵程序的堆空间。brk
设置堆的结束地址,sbrk
增加堆的大小。 虽然 mmap
更常用,但理解 brk
的工作方式也有助于更深入地了解内存管理。
实现步骤:
- 调用
brk(0)
获取当前堆的结束地址。 - 计算所需的额外空间大小。
- 使用
brk(new_break)
调整堆的大小,其中new_break
是新的堆结束地址。 - 将数据写入新分配的堆空间。
- 在程序结束时,理论上可以调用
brk
恢复到初始堆大小(虽然实际中不常这样做,操作系统在进程退出时会自动回收内存)。
代码示例:
section .data
message db "Hello, brk!", 0
section .text
global _start
_start:
; Get current break (end of heap)
mov rax, 12 ; SYS_brk
mov rdi, 0 ; Request current break address
syscall
; Save current break
mov r12, rax
; Calculate required size (string length + null terminator)
; In real applications use `strlen` function or known string length instead of hardcoding values
; Increase heap size to accomodate new String
mov rax, 12 ; SYS_brk
add rax, 14 ;Increase the rax by our String size
mov rdi, rax ;new break pointer
syscall
cmp rax, -ENOMEM; check return value. Jump to error if negative
jl error_handler
; Copy string into newly allocated space (replace source and destination indexes)
mov rsi, message
mov rdi, r12
call string_copy;Same routine
; Print String
mov rsi, r12 ; pass the start address where data where copied
call .print; print routine (given code)
;Exit program
mov rax, 60
xor rdi, rdi
syscall
; String copy function
string_copy:
push rbp
mov rbp, rsp
.copy_loop:
mov al, [rsi]
cmp al, 0
je .copy_end
mov [rdi], al
inc rsi
inc rdi
jmp .copy_loop
.copy_end:
pop rbp
ret
;---print routine
.print:
push rcx ; save increment if used in another function
xor rcx, rcx ; reset increment to 0
.print_loop:
cmp byte [rsi + rcx], 0 ; check if null
je .print_done ; if yes print the value beacause end of string
inc rcx ; else increment
jmp .print_loop ; and restart the operation
.print_done:
mov rdx, rcx ; pass the string size as parameter syscall sys_write
mov rax, 1 ; prepare syscall sys_write function
mov rdi, 1 ; prepare syscall stdout output
syscall ; print the string
pop rcx ; get the old increment value to handle return
ret
;-------------ERROR HANDLER----------
error_handler:
;Basic error handling logic. Print error message (if applicable) and terminate execution
mov rax, 60
mov rdi, 1
syscall
安全建议:
- 需要进行错误处理以防止请求过大的堆空间。
- 在多线程程序中使用
brk
需要进行适当的同步,因为堆是所有线程共享的资源。
结论
处理 x64 Linux 汇编中的可变长度字符数组需要理解内存管理的底层机制。mmap
提供了更灵活和常用的方法来动态分配内存,而理解 brk
有助于更深入地了解堆的工作方式。选择哪种方法取决于具体的应用场景和需求。始终记住,仔细的内存管理和安全措施是编写健壮和安全汇编代码的关键。