x86_64汇编中创建栈缓冲区打印数字及段错误排查
x86_64 NASM汇编:解决_printNum函数段错误与栈缓冲区创建方法
在Linux环境下使用NASM编写x86_64汇编代码时,实现_printNum函数打印RAX中存储的数字,出现乱码O=N84NioR/\_dsPfec IS/pRDNui/0SmL3l及Segmentation fault错误。尝试两种实现方式均触发段错误:
- 第一种:逐个弹出余数转ASCII后调用sys_write打印
- 第二种:将转换后的ASCII字符存入缓冲区再打印
经调试确认,问题根源在于未正确使用栈上缓冲区存储数字字符,需将转换后的字符存入栈上缓冲区后,再将缓冲区地址传递给sys_write的rsi参数。以下是两种错误实现的代码,以及正确的栈缓冲区创建与使用方法。
第一种错误实现代码
_printNum: ;prints number stored in RAX push rax ;push number onto stack mov rcx, 0 ;set counter to 0 mov rbx, 10 ;set divisor to 10 _pnl: ;loop1 - extract digits mov rdx, 0 ;set rdx to 0 div rbx ;divide number by 10 push rdx ;push remainder inc rcx ;increment counter cmp rax, 0 ;repeat until quotient = 0 jne _pnl _pnl2: ;loop2 - reverse and print mov rdi, 1 ;stdout mov rdx, 1 ;length of 1 byte pop rax ;pop remainder lea rsi, [rax + '0'];convert digit into ASCII character and move it into rsi mov rax, 1 ;sys_write syscall ;print digit dec rcx ;decrement counter cmp rcx, 0 ;repeat until counter = 0 jne _pnl2 pop rax ;restore number into rax ret
错误分析:lea rsi, [rax + '0'] 是非法操作,sys_write要求rsi指向合法的内存地址,此处直接使用寄存器+常量的偏移并非可访问内存,触发段错误。
第二种错误实现代码
_printNum: ;prints number stored in RAX push rax ;push number onto stack mov rcx, 0 ;set counter to 0 mov rbx, 10 ;set divisor to 10 _pnl: ;loop1 - extract digits mov rdx, 0 ;set rdx to 0 div rbx ;divide number by 10 push rdx ;push remainder inc rcx ;increment counter cmp rax, 0 ;repeat until quotient = 0 jne _pnl _pnl2: ;loop2 - reverse and print pop rax ;pop remainder add rax, '0' ;convert digit to ASCII mov [rsi + rcx], rax;add digit to buffer dec rcx ;decrement counter cmp rcx, 0 ;repeat until counter = 0 jne _pnl2 mov rax, 1 ;sys_write mov rdx, rcx ;length of number mov rdi, 1 ;stdout syscall ;print number pop rax ;restore number into rax ret
错误分析:rsi未初始化,指向随机内存地址,写入该地址触发段错误;同时最后mov rdx, rcx时rcx已为0,导致sys_write打印长度为0,无法输出内容。
正确实现:栈上创建缓冲区打印数字
_printNum: push rbx ; 保存被调用者保存寄存器(x86_64 SysV ABI要求) push rcx push rdx mov rcx, 0 ; 数字位数计数器 mov rbx, 10 ; 除数为10 ; 第一步:分解数字,将余数压栈 _pn_extract: xor rdx, rdx ; 清零rdx,div使用rdx:rax作为被除数 div rbx ; rax = rax / 10, rdx = rax % 10 push rdx ; 保存余数 inc rcx ; 位数+1 test rax, rax ; 判断商是否为0 jnz _pn_extract ; 第二步:在栈上开辟缓冲区,将余数转换为ASCII存入缓冲区 sub rsp, 20 ; 开辟20字节缓冲区(足够存储64位整数的所有数字) mov rsi, rsp ; rsi指向栈缓冲区起始地址 mov rdx, rcx ; 保存数字长度到rdx _pn_convert: pop rax ; 弹出余数 add al, '0' ; 转换为ASCII字符(用al避免高位干扰) mov [rsi], al ; 存入缓冲区 inc rsi ; 缓冲区指针后移 dec rcx jnz _pn_convert ; 第三步:调用sys_write打印缓冲区内容 mov rax, 1 ; sys_write系统调用号 mov rdi, 1 ; stdout文件描述符 syscall ; 恢复栈空间和寄存器 add rsp, 20 ; 释放栈缓冲区 pop rdx pop rcx pop rbx ret
栈缓冲区创建说明
- 开辟缓冲区:使用
sub rsp, size指令在栈上预留空间,此处预留20字节足够存储64位无符号整数的最大位数(20位)。 - 获取缓冲区地址:rsp寄存器此时指向缓冲区起始地址,将其赋值给rsi即可作为sys_write的内存参数。
- 释放缓冲区:函数返回前用
add rsp, size恢复栈指针,避免栈溢出。 - 寄存器保护:x86_64 SysV ABI中rbx、rcx、rdx属于被调用者保存寄存器,需在函数开头压栈保存,结尾弹栈恢复。
内容的提问来源于stack exchange,提问作者cuper
相关产品推荐
相关产品推荐

