【问题标题】:Assembly 8086- Pushing and popping registers from stack not working程序集 8086 - 从堆栈中推送和弹出寄存器不起作用
【发布时间】:2017-08-08 09:40:25
【问题描述】:

我是一个初学者,出于某种原因,从堆栈中推入和弹出并不适合我。我的引导程序:

org 0x7c00
bits 16
jmp main
print:
    pop bx
    mov al, [bx]
    mov ah, 0eh
    int 10h
    ret
main:
    mov bx, msg
    push bx
    call print

    cli
    hlt

    msg: db 'Hello World!', 0
    times 510 - ($-$$) db 0
    dw 0xAA55

我相信这应该做的是,将地址msgbx 压入堆栈,然后将其检索到bx。然而,情况似乎并非如此。 'H' 不会被打印出来。而是打印 '-'。如果我使用msg 作为有效地址,它就可以工作。

编辑:正如 Duncan 指出的,call 指令将返回地址压入堆栈顶部,这使得上述程序使用该返回地址进行 BIOS 中断!我现在把pop的返回地址改成dx然后把pop改成bx,完成后使用bxjmp的值改成dx

org 0x7c00
bits 16
jmp main
print:
    pop dx
    pop bx
    mov al, [bx]
    mov ah, 0eh
    int 10h
    jmp dx
main:
    mov bx, msg
    push bx
    call print

    cli
    hlt

    msg: db 'Hello World!', 0
    times 510 - ($-$$) db 0
    dw 0xAA55

【问题讨论】:

  • 当然还有dx是否保留在int 10h调用上的问题。
  • 那个“修复”是不合理的复杂 IMO。而且脆弱..如果有的话,您可以在pop bx之后立即执行push dx,然后使用ret,因此您无需担心dx,但如果您坚持通过堆栈传递参数,您应该宁可做冗长的 push bp mov bp,sp naive-C 堆栈框架的事情(然后 mov bx,[bp+4] 是第一个参数),因为这对于任何有经验的 ASM 程序员来说都很容易阅读,所以以后会更容易帮助你.

标签: assembly x86-16 qemu


【解决方案1】:

call 指令将程序计数器压入堆栈,ret 从堆栈中弹出顶部值并跳转到它。

因此,您不能通过在调用之前将参数推送并在调用中弹出它们来将参数传递给函数,因为保存的 pc 会妨碍。

选项可能包括设置帧指针,以便您可以访问仍在堆栈上的参数,然后将它们作为返回的一部分弹出。

或者对于这么简单的事情,您可以将返回地址弹出到另一个寄存器中,而不是返回,而是直接跳转到它。

【讨论】:

  • 如果你知道返回地址的长度,和/或其他推送的值,你可以直接寻址,例如:mov bx,2[sp]
  • 这是汇编语言的好习惯吗?我来自 OOP 背景。
  • @ArjavGarg 最好先学习汇编基础知识,然后再尝试编写像引导加载程序这样棘手的东西,在那里你从一开始就与非常恶劣的环境作斗争(作为 BIOS 供应商,初始状态非常模糊和错误)不用太在意,加上几乎没有可调用的服务,除了 BIOS 服务)。我仍然对编写自己的引导加载程序甚至裸机操作系统如此吸引人感到困惑,而不是使用一些稳定的操作系统环境来编写一些有趣的东西,比如玩一些图形和提供可用的 GUI 调试器。 OS-dev 更加棘手。
  • 我已经完成了 OpenGL 和 2d Java 图形。这似乎是下一个合乎逻辑的步骤
  • @ArjavGarg 对我来说,下一个合乎逻辑的步骤听起来像是在汇编中进行 OGL + 2D,因为您已经知道了原理,并且可以专注于学习汇编本身,熟悉基本指令,算术,内存管理,堆栈使用等......然后引导加载程序(至少初始部分)必须处于 16b 实模式,这是另一个 PITA,所以......是的,没关系,如果你想做引导加载程序,没关系,只是不要指望它会很容易,它会比任何 Java 都更容易咬你并适得其反,需要更多地关注细节和微观管理一切
【解决方案2】:

jmp far 0:main 开始,将cs:ip 标准化,因为有些BIOS 会用cs=0 调用你,有些用cs=0x07c0

print:
    pop bx
    mov al, [bx]
    mov ah, 0eh
    int 10h
    ret

也许你在 shell 漏洞利用中看到过类似的东西,但正确的用法是这样的:

    call print  ; this will put `msg` at top of stack!
msg: db 'H'
print:
    pop bx      ; load `msg` address
    ... no RET !! (there's nowhere to return to)

这是一个技巧,当您不知道代码将位于何处时,如何将正确的偏移量放入堆栈(shell 漏洞利用通常会进入某个缓冲区溢出区域,并带有一些随机地址)。

这在引导加载程序中不需要,因为您处于固定位置 0000:7C00 并且您可以按此方式进行组装,因此对 print 等自定义过程使用一些自定义“通过寄存器传递参数值”调用约定很好,无需涉及完全堆栈(除了使用它来存储 call + ret 对的返回地址)。


在使用堆栈之前也要初始化堆栈,例如,在 main 的开头你可能想要这样做:

main:
    ; set ss:sp to 0000:7C00 (so you have about ~29kB of RAM for stack)
    xor ax,ax
    mov ss,ax       ; disables interrupts for 1 more instruction
    mov sp,0x7C00   ; so sp set must follow the `ss` setup

当然不要将它用于任何深度递归,29kB 是小堆栈(但对于合理编写的引导加载程序来说已经足够了,只从磁盘加载一些内核,实际上 100-200B 的堆栈应该足够了)。

当然还要设置ds!正如你所做的mov al,[bx] 一样,ds 已经设置好了。在您的情况下,最简单的方法是将所有内容保留在0000:7C00,因此在mov ss,ax 之前您也可以使用mov ds,ax

【讨论】:

  • @ArjavGarg 在wiki.osdev.org/Main_Page 上花费数周时间阅读和尝试其他资源。您甚至可能想阅读grub 的源代码,它必须在现实世界的场景中工作。我自己从来没有做过引导加载程序,对我来说太无聊了,我更喜欢真正的应用程序编码,所以我不知道编写引导加载程序可能出现的所有问题,但肯定不可能将它们放入一些简短的答案中,因为至少可以几十页的文字。你所拥有的可能已经在 qemu 或 bochs 下工作了,我认为这两个选项都可以以某种方式进行调试?肯定是博克斯。
猜你喜欢
  • 2020-02-06
  • 1970-01-01
  • 1970-01-01
  • 2016-12-04
  • 1970-01-01
  • 2017-08-20
  • 2014-10-02
  • 1970-01-01
  • 2015-06-17
相关资源
最近更新 更多