我正在做 Linux 汇编,我知道它有一个平面内存模型。我对 NEAR 和 FAR JMP 感到困惑。
NEAR 在同一个段,而 FAR 是另一个段。据我了解,linux虚拟内存中没有段?另外,我们如何知道我的程序代码是否按多个段布局?
现在已经很久没有分段了。保护模式 x86 中的正确术语是选择器。
话虽如此,近跳转和远跳转之间的区别在于前者保持相同的代码选择器cs
,而后者(通常)会更改它。
在平面内存模型中,前一种情况几乎总是如此。
您可能有一个操作系统,其中平面内存模型由多个选择器提供服务,但我看不到它的有用用例,而且这不是 Linux 的工作方式,至少在 x86 上是这样。
NEAR 在同一个段,而 FAR 是另一个段。
近跳转跳转到当前代码段中的一个位置(由 指向cs
)。远跳转通常用于跳转到不同代码段中的位置,但如果远地址中的段选择器与 中的值一致,它也可以跳转到当前段中的位置cs
。
据我了解,linux虚拟内存中没有段?
如果发现使用某种分段内存的 CPU 的 Linux 端口,我不会感到惊讶。所以,我会说这取决于。不过,您不太可能在 x86 平台上看到 Linux 使用段。但同样,您或其他人可以制作一个在实模式下运行并使用段的小型 Linux。
另外,我们如何知道我的程序代码是否按多个段布局?
检查 CPU 和操作系统。当然,如果您编写可移植的 C 代码,那么您应该不会担心这一点。
据我了解,linux虚拟内存中没有段?
它足够准确。有特定于线程的数据%fs
,其位置由段base指向,但没有适合远跳转的段。
另外,我们如何知道我的程序代码是否按多个段布局?
如果您的目标平台是 Linux,那么您已经知道它不是。(如果任何jump far
现代操作系统仍然以有意义的方式使用段,我会感到惊讶)。
现代主流操作系统(如 Linux)中使用的平面内存模型使分段大多过时了,而且(幸运的是)你不需要担心。
在页表支持 NX 位将页面标记为不可执行之前,有一些工作是使用段限制来避免执行可写内存(尤其是堆栈),这使得缓冲区溢出漏洞比仅仅返回到 shellcode 缓冲区更难。例如2003 年的Exec Shield(lwn 文章)。
我忘记了这实际上是如何工作的,我认为这主要是在排除堆栈的 CS 上设置段限制,而不是为每个代码块(主可执行文件 + 每个动态库)使用带有新段描述符的 far jmp。
但幸运的是,现代 x86 可以使用具有 NX 位(PAE 或 x86-64)的现代页表,这意味着用户空间可以像读取和写入权限一样设置正常的每页执行权限(使用mmap
、mprotect
和 ELF 元数据对于程序的初始部分,如堆栈、r/w 数据和文本 + 只读数据)。或者对于非 Linux,当然是它们等效的系统调用和元数据。
但是如果操作系统是 Linux 并且已经在保护模式 + 平面内存模式下运行,那么我们还需要 Far JMP 吗?
不,在 Linux 上您永远不需要far jmp
处于用户空间或内核模式,并且制作一个是个坏主意。
您可能很想使用 farjmp ptr16:32
编码直接跳转到绝对地址(新的 CS 值被硬编码为 Linux 已知用于 32 位用户空间的相同 CS 值)。但这比普通的 near 慢很多jmp rel32
,它可以从任何其他 32 位地址到达任何 32 位地址。(直接近跳转仅适用于相对位移,而不是绝对目标。如果您不知道自己的地址来计算相对位移,则需要间接跳转才能近跳转到绝对地址。)
这甚至不是 64 位模式的选项,其中没有jmp far 80-bit immediate
ptr16:64 编码,只有内存间接。因此,如果跳转目标距离编码太远,您会像普通人一样使用mov rax, imm64
/ 。jmp rax
rel32
Linux 上的所有用户空间进程都使用相同的 32 位或 64 位 CS 段选择器(当前特权级别 CPL = 3 = ring 3 用户模式),内核使用不同的(CPL=0 = ring 0 内核模式)。
现代 x86 操作系统上 CS 的唯一目的是选择 32 位和 64 位模式(.L
GDT 条目中的位)和特权级别。
您只能通过中断/异常和指令在用户和内核 CS 之间切换int
,sysenter
或syscall
进入内核模式,从内核堆栈iret
恢复cs:eip
或从(32 位内核)或从系统调用优化返回用户空间。在首先进入保护模式(带有)后,内核不会更改 CS。cs:rip
sysexit
sysret
jmp far
jmp far
除非您想在以 64 位启动的进程中更改为 32 位模式等不稳定的愚蠢计算机技巧,否则jmp far
在 Linux 下是零理由。
这是可能的,但我不知道它是否真的稳定。例如,内核可能会记住您的进程应该是 64 位的,并以 64 位模式从中断返回。(即异步将 CS 设置为硬编码USER32_CS
常量,而不是恢复旧值。) IIRC,它在syscall
使用的返回路径中执行此操作sysret
,请参阅如果您在 64 位中使用 32 位 int 0x80 Linux ABI 会发生什么代码?
你想这样做吗?你不可以。除了具有BITS 32
vs.BITS 64
指令的汇编程序之外,任何工具链都对这样做的支持为零,基本上是零收益,并且崩溃的风险很大(您的进程,而不是机器)。在 32 位模式下您可以在手写 asm 中执行的任何操作,您可以在 64 位模式下使用分配的 32 位指针mmap(MAP_32BIT)
或使用 x32 ABI 执行同样的操作。
我猜也许在原始 Core 2 上(其中 cmp/jcc 宏融合仅在 32 位模式下工作),在 32 位模式下运行循环并且仅使用 64 位模式来接触很多可能会有性能优势内存,但切换基本上会花费管道刷新,因此通常只展开一点会更便宜,而不是切换到 32 位模式并返回 64 以进行特定的长时间运行循环。
FAR 和 NEAR 控制转移指令基本上是一种控制转移协议 通常,我们看到程序按顺序从上到下逐行执行,有时需要将控制从一个位置转移到另一个 NEAR - 如果您想将控制转移到当前代码段内的内存位置,则称为 NEAR(段内)如果将控制转移到当前代码段之外,则在 FAR 中称为 FAR 跳转,因为控制在当前代码段之外传递 CS(代码段) 和 IP(指令指针) 必须更新为新值