【问题标题】:Near and Far JMPs近和远 JMP
【发布时间】:2013-01-26 13:20:24
【问题描述】:

我正在做 Linux 汇编,我知道它有一个平面内存模型。我对 NEAR 和 FAR JMP 感到困惑。

NEAR 在同一个段,而 FAR 是另一个段。据我了解,linux虚拟内存中没有段?另外,我们如何知道我的程序代码是否按多个段布局?

【问题讨论】:

    标签: assembly x86 nasm


    【解决方案1】:

    已经很久没有分段了。保护模式 x86 中的正确术语是 selector。

    话虽如此,近跳转和远跳转的区别在于前者保持相同的代码选择器cs,而后者(通常)会更改它。

    在平面内存模型中,前一种情况几乎总是如此。

    可以拥有一个操作系统,其中平面内存模型由多个选择器提供服务,但我看不到它的有用用例,而且这不是 Linux 的工作方式,至少在x86。

    【讨论】:

    • 当我必须为 FAR JMP 更改选择器时,您能举个例子吗?我对现实世界的使用感到困惑
    • @ST-User:当然。假设您有一个操作系统,其中底部 3G 是用户代码,并且操作系统允许自修改代码。这意味着用户代码选择器将允许读/写访问。但是,操作系统服务位于顶部 1G 中,并且不希望您弄乱它,因此该选择器将是只读的(好吧,也可以执行)。这是一种可能性。但请记住,这是一个人为的例子——我知道没有现代操作系统会仅仅因为他们更喜欢保持简单而这样做。
    • 你的意思是在现代操作系统中根本不需要 Far Jmp 吗?它只适用于我们使用段寄存器来定位段的较旧的 16 位东西?
    • @ST-User:是的,虽然它不一定相当那么简单(因为“平面内存”在多个层面上都有意义。对于外行来说,你可以考虑一下每个进程获得一个 4G 地址空间(在“keep-it-simple”操作系统中)并且在该 4G 中只需要 一个 代码选择器、一个数据选择器和一个堆栈选择器。在这种情况下,不远永远需要跳转。在更复杂的情况下,您可能会将代码分散在 许多 个不同的代码区域中,每个区域都有自己的选择器 - 这需要远跳转。
    • 作为一名汇编语言程序员,我如何知道我的代码分布在多个段上?然后使用 far jmp?
    【解决方案2】:

    NEAR 在同一个段,而 FAR 是另一个段。

    近跳转跳转到当前代码段中的一个位置(由cs 指向)。远跳转通常用于跳转到不同代码段内的位置,但如果远地​​址中的段选择器与cs 中的值一致,它也可以跳转到当前段内的位置。

    据我了解,linux 虚拟内存中没有段?

    如果发现 Linux 端口使用某种分段内存连接到 CPU,我不会感到惊讶。所以,我会说这取决于。不过,您不太可能在 x86 平台上看到 Linux 使用段。但同样,您或其他人可以制作一个在实模式下运行并使用分段的小型 Linux。

    另外,我们如何知道我的程序代码是否分多个段布局?

    您检查 CPU 和操作系统。当然,如果您编写可移植的 C 代码,这应该与您无关。

    【讨论】:

    • 你能给我一个 far jmp 的真实例子吗?我很困惑什么时候加上“远”前缀说我在汇编中编写了一个非常大的程序。
    • 如果它是一个非常大的 16 位程序,并且它的代码不适合一个 64KB 段,则将其拆分为多个段(每个段小于 64KB),然后使用任一远呼叫或远跳。显然,远调用和远返回必须匹配,您不应该使用近调用,然后使用远返回返回,反之亦然。
    • 不,我指的是使用保护模式 + 平面内存模型的现代 IA-32 驱动的 Linux 操作系统的示例。不是较旧的 16 位。
    • 您仍然可以在保护模式下以类似的方式使用段,只是现在您需要在 GDT(或 LDT)中有段描述符来明确定义段,这与实模式不同,其中唯一可变的段属性是段基础,它可以直接从选择器/段计算。在保护模式下,更多用途是可能的。对不起,你的问题还不清楚。想了解更多可能的选择吗?阅读英特尔 CPU 手册。在这里重述是不切实际的。
    【解决方案3】:

    据我了解,linux 虚拟内存中没有段?

    足够准确。有特定于线程的 data,其位置由 %fsbase 指向,但没有适合远跳转的段。

    另外,我们如何知道我的程序代码是否以多种方式布局 细分?

    如果您的目标平台是 Linux,那么您已经知道它不是。 (如果任何现代操作系统仍然以使jump far 有意义的方式使用段,我会感到惊讶)。

    【讨论】:

    • 为什么我们甚至需要远 jmps?当我们不更改细分时?
    • 在具有平坦内存模型的现代系统上,您不需要远跳转,除非在 非常 特殊情况下,例如 进入 保护模式。
    • 但是如果操作系统是 Linux 并且已经在保护模式 + 平面内存模式下运行,那么我们还需要 Far JMP 吗?
    • @ST-User:不,在 Linux 的用户空间或内核模式下,您永远不需要 far jmp。所有用户空间进程使用相同的 CS 段选择器(CPL=3 = ring 3 用户模式),内核使用不同的选择器(CPL=0 = ring 0 内核模式)。您只能通过中断/异常和指令在它们之间切换,例如intsysentersyscall 进入内核模式,iret 从内核堆栈恢复cs:eipcs:rip,或sysret。除非您想在以 64 位启动的进程中执行不稳定的愚蠢计算机技巧,例如更改为 32 位模式,否则jmp far 的理由为零。
    【解决方案4】:

    现代主流操作系统(如 Linux)中使用的平面内存模型使得分段大多已过时,而且(幸运的是)您无需担心。

    在页表支持 NX 位将页面标记为不可执行之前,有一些工作是使用段限制来避免执行可写内存(尤其是堆栈),这使得缓冲区溢出攻击比仅仅返回到外壳代码。例如Exec Shield (lwn article) 从 2003 年开始。

    我忘记了这实际上是如何工作的,我认为这主要是在排除堆栈的 CS 上设置了段限制,而不是为每个代码块(主可执行文件 + 每个动态库)使用带有新段描述符的 far jmp。

    但幸运的是,现代 x86 可以使用具有 NX 位(PAE 或 x86-64)的现代页表,这意味着用户空间可以像读取和写入权限一样设置正常的每页执行权限(@987654324 @、mprotect 和程序初始部分的 ELF 元数据,如堆栈、r/w 数据和文本 + 只读数据)。或者对于非 Linux,当然是它们等效的系统调用和元数据。

    但如果操作系统是 Linux 并且已经在保护模式 + 平面内存模式下运行,那么我们还需要 Far JMP 吗?

    不,在 Linux 的用户空间或内核模式下,您永远不需要 far jmp 而且创建一个是个坏主意。

    您可能很想使用远 jmp ptr16:32 编码直接跳转到绝对地址(新的 CS 值被硬编码为 Linux 已知用于 32 位用户的相同 CS 值-空间)。但这比jmp rel32 附近的普通地址要慢得多,后者可以从任何其他 32 位地址到达任何 32 位地址。 (直接近跳转仅适用于相对位移,而不是绝对目标。如果您不知道自己的自己的地址来计算相对位移,则需要间接跳转以近跳转到绝对地址。 )

    这在 64 位模式下甚至都不是一个选项,其中没有 jmp far 80-bit immediate ptr16:64 编码,只有内存间接。因此,如果跳转目​​标对于rel32 编码来说太远,您可以像普通人一样使用mov rax, imm64 / jmp rax


    Linux 上的所有用户空间进程使用相同的 32 位或 64 位 CS 段选择器(当前特权级别 CPL = 3 = 环 3 用户模式),内核使用不同的(CPL=0 = ring 0 内核模式)。

    现代 x86 操作系统上 CS 的唯一目的是选择 32 位和 64 位模式(GDT 条目中的 .L 位)和权限级别。

    您只能通过中断/异常和指令在用户和内核 CS 之间切换,例如 intsysentersyscall 进入内核模式,iret 从内核恢复 cs:eipcs:rip堆栈,或sysexit(32 位内核)或sysret,用于优化从系统调用返回到用户空间。在首先进入保护模式(带有jmp far)后,内核不会jmp far 更改CS。


    除非您想在以 64 位启动的进程中执行不稳定的愚蠢计算机技巧,例如更改为 32 位模式,否则在 Linux 下jmp far 是零理由

    可能的,但我不知道它是否真的稳定。例如内核可能记得您的进程应该是 64 位的,并从 64 位模式的中断返回。 (即异步将 CS 设置为硬编码的USER32_CS 常量,而不是恢复旧值。)IIRC,它在使用sysretsyscall 返回路径中执行此操作,请参阅What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?

    你想这样做吗?你不可以。除了具有BITS 32BITS 64 指令的汇编程序之外,任何工具链都对这样做的支持为零,基本上为零收益,并且崩溃的风险很大(您的进程,而不是机器)。在 32 位模式下您可以在手写 asm 中执行的任何操作,在 64 位模式下使用分配有 mmap(MAP_32BIT) 的 32 位指针或使用 x32 ABI 也可以执行。

    我猜可能在原始 Core 2 上(其中 cmp/jcc 宏融合仅在 32 位模式下工作),在 32 位模式下运行循环并且仅使用 64 位模式可能会有性能优势会占用大量内存,但切换基本上会消耗管道刷新,因此通常只展开一点会更便宜,而不是切换到 32 位模式并返回 64 以进行特定的长时间运行循环。

    【讨论】:

      【解决方案5】:

      FAR 和 NEAR 控制传输指令基本上是一种控制传输协议 通常,我们看到程序按顺序从上到下逐行执行,有时需要将控制从一个位置转移到另一个位置 NEAR - 如果您想将控制权转移到当前代码段中的内存位置,那么它被称为 NEAR(内部段) 如果控制转移到当前代码段之外,则称为 FAR 跳转 在 FAR 中,因为控件在当前代码段之外传递,CS(代码段)和 IP(指令指针)都必须更新为新值

      【讨论】:

        猜你喜欢
        • 2013-11-07
        • 2013-05-09
        • 2023-04-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-11-27
        • 1970-01-01
        • 2015-12-18
        相关资源
        最近更新 更多