You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

pusha与popa指令工作原理及自制简易OS打印函数调用异常原因

问题分析:print_string仅打印首个字符串的原因

问题重现

你编写的第一个print_string实现存在栈溢出问题,导致程序在打印完第一个字符串后崩溃,无法执行后续代码。

第一个版本的print_string代码:

print_string:
    pusha
    cmp byte[bx], 0    
    je exit             
    mov ah, 0x0e        
    mov al, [bx]        
    int 0x10            
    inc bx               
    jmp print_string    
    
    exit: 
        mov ah, 0x0e       
        mov al, 13          
        int 0x10            
        mov al, 10          
        int 0x10            
        popa
        ret                 

主调用代码:

[org 0x7c00]              

mov bx, hello               
call print_string           
mov bx, hi                 
call print_string          
jmp $                       

%include "print_string.s"   
hello:                  
    db "Hello, World!",0     
hi:
    db "This is a test.",0

times 510-($-$$) db 0       
dw 0xaa55                   

问题根源

你的print_string用jmp print_string做循环,每次循环都会重新执行pusha指令:

  1. 第一次调用print_string时,执行pusha把8个通用寄存器压入栈;
  2. 打印一个字符后inc bx,然后jmp print_string回到函数开头,再次执行pusha,又把8个寄存器压栈;
  3. 这个过程会重复到字符串结束,Hello, World!有13个字符,就会执行13次pusha,栈里堆积了13×16=208字节的数据;
  4. 实模式下BIOS留给引导扇区的栈空间非常有限(通常只有几百字节),反复压栈会直接导致栈溢出,程序执行到exit段的popa和ret时已经处于异常状态,根本无法回到主函数执行mov bx, hi和第二次call print_string。

而修改后的版本把pusha/popa移到主函数,print_string内部没有pusha,循环时不会反复压栈,栈空间不会被耗尽,因此能正常执行两次调用。

pusha和popa的工作原理

  • pusha:一次性将8个16位通用寄存器按固定顺序压入栈,顺序是:ax → cx → dx → bx → sp(压入pusha之前的栈指针值) → bp → si → di。每个寄存器占2字节,所以一次pusha会占用16字节的栈空间。
  • popa:按与pusha相反的顺序从栈中弹出数据到寄存器,顺序是:di → si → bp → sp(这个值会被忽略,栈指针仍按正常弹出逻辑推进) → bx → dx → cx → ax,刚好恢复执行pusha之前的所有通用寄存器状态。

修复建议

如果想在print_string内部保留pusha/popa,应该把递归式的jmp print_string改成循环结构,避免反复执行pusha:

print_string:
    pusha          ; 仅在函数开头压栈一次
print_loop:
    cmp byte[bx], 0    
    je exit             
    mov ah, 0x0e        
    mov al, [bx]        
    int 0x10            
    inc bx               
    jmp print_loop       ; 跳转到循环体开头,而非函数开头
    
exit: 
    mov ah, 0x0e       
    mov al, 13          
    int 0x10            
    mov al, 10          
    int 0x10            
    popa                ; 函数结束时仅弹栈一次
    ret                 

这样pusha和popa只会执行一次,不会导致栈溢出,同时也能正确保护寄存器状态。

内容的提问来源于stack exchange,提问作者Davy133

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 04:05:19