x64 Linux汇编可变字符数组实现方案咨询
现有背景
你当前在x64 Linux环境下实现的字符串打印函数如下:
.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
当前使用.bss段固定大小缓冲区,存在字符串过长需重新定义、拼接易溢出的问题,且尝试过的几种思路均有痛点:不愿用brk/mmap,栈上分配觉得不规范且浪费内存,BSS段单独定义缓冲区过于繁琐。
以下是无需brk/mmap的可行实现方案:
方案一:栈上动态复用缓冲区(符合调用规范)
栈上分配局部缓冲区是完全规范的做法,只要遵循x64 System V调用约定(保持rsp16字节对齐),用完后恢复栈指针即可。针对内存浪费问题,可以按需分配:先计算所需字符串的总长度,一次性分配刚好足够的栈空间,避免冗余。
示例:栈上分配缓冲区实现整数转字符串
; 输入:rax = 要转换的整数 ; 输出:直接打印字符串(无需保存到全局缓冲区) .int_to_print: push rbx push rbp mov rbp, rsp sub rsp, 32 ; 预分配32字节(足够存64位十进制整数的字符串) mov rbx, rsp ; rbx指向缓冲区起始 mov rsi, 10 ; 除数10 .int_loop: xor rdx, rdx div rsi ; rax = 商,rdx = 余数 add dl, '0' ; 转ASCII dec rbx mov [rbx], dl test rax, rax jnz .int_loop ; 调用.print函数打印,rsi指向缓冲区起始,长度为rsp+32 - rbx mov rsi, rbx call .print mov rsp, rbp ; 恢复栈指针 pop rbp pop rbx ret
注意:分配栈空间时,sub rsp, size后要确保rsp是16字节对齐(x64 System V要求调用函数前rsp对齐16字节,syscall也需要),如果size不是16的倍数,可调整为最近的16倍数。
方案二:BSS段全局缓冲区池(复用减少定义)
不用为每个场景单独定义缓冲区,而是在BSS段创建一个全局缓冲区池,用指针跟踪当前可用位置,实现缓冲区复用。
示例:
section .bss buffer_pool resb 1024 ; 1KB的全局缓冲区池 pool_ptr resq 1 ; 跟踪池内可用位置的指针 section .text ; 初始化池指针(程序启动时调用) .init_pool: mov qword [pool_ptr], buffer_pool ret ; 从池内分配指定长度的缓冲区 ; 输入:rdi = 所需长度 ; 输出:rax = 缓冲区起始地址(失败返回0) .alloc_from_pool: push rbx mov rbx, [pool_ptr] add rbx, rdi cmp rbx, buffer_pool + 1024 ; 检查是否超出池大小 jae .alloc_fail mov rax, [pool_ptr] mov [pool_ptr], rbx pop rbx ret .alloc_fail: xor rax, rax pop rbx ret ; 重置缓冲区池(回收所有空间) .reset_pool: mov qword [pool_ptr], buffer_pool ret
使用时,先调用.init_pool初始化,然后用.alloc_from_pool获取缓冲区,用完后可调用.reset_pool重置,或者继续分配后续空间(适合一次性拼接多个字符串)。
方案三:直接分段打印(完全避免缓冲区)
如果你的需求只是打印字符串,不需要保存拼接后的结果,可以直接分段调用sys_write,完全跳过缓冲区。比如整数转字符串时,每计算出一位就打印一位,拼接多个字符串时依次打印各部分。
示例:无缓冲区的整数打印
; 输入:rax = 要打印的整数 .int_direct_print: push rbx push rcx push rdx mov rbx, 10 xor rcx, rcx ; 计数位数 ; 先把所有余数压栈 .int_push: xor rdx, rdx div rbx push rdx inc rcx test rax, rax jnz .int_push ; 依次弹出并打印 .int_pop_print: pop rdx add dl, '0' mov [rsp-1], dl ; 临时存到栈上 mov rax, 1 mov rdi, 1 mov rsi, rsp-1 mov rdx, 1 syscall dec rcx jnz .int_pop_print pop rdx pop rcx pop rbx ret
这种方式不需要任何缓冲区,适合简单的打印场景,缺点是系统调用次数较多,但对于小型程序影响可忽略。
内容的提问来源于stack exchange,提问作者vincent clerc

