Near and Far JMPs

前端 未结 5 1159
梦谈多话
梦谈多话 2021-02-05 23:56

I am doing Linux assembly and I understand that is has a flat memory model. What I am confused about is NEAR and FAR JMPs.

NEAR is in the same segment while FAR is anot

5条回答
  •  别跟我提以往
    2021-02-06 00:36

    Flat memory models used in modern mainstream OSes like Linux make segmentation mostly obsolete, and (fortunately) not something you ever need to worry about.

    Before page tables supported a NX bit to mark pages as non-executable, there was some work on using segment limits to avoid execution of writeable memory (especially the stack), making buffer overflow exploits harder than just returning into a buffer of shellcode. e.g. Exec Shield (lwn article) from 2003.

    I forget how this actually worked, I think it was mostly just setting a segment limit on CS that excluded the stack, not using far jmp with a new segment descriptor for each block of code (main executable + each dynamic library).

    But fortunately modern x86 can use modern page tables with a NX bit (PAE or x86-64), meaning user-space can have normal per-page execute permission set up the same way as read and write permissions (with mmap, mprotect, and ELF metadata for the initial parts of the program like the stack, r/w data, and text + read-only data). Or for non-Linux, their equivalent system calls and metadata of course.

    But if the OS is Linux and already running in protected mode + flat memory model, then do we ever need Far JMPs?

    No, you don't ever need a far jmp in user-space or kernel mode on Linux, and it would be a bad idea to make one.

    You might be tempted to use a far jmp ptr16:32 to encode a direct jump to an absolute address (with the new CS value being hard-coded as the same CS value that Linux is known to use for 32-bit user-space). But that's a lot slower than a normal near jmp rel32, which can reach any 32-bit address from any other 32-bit address. (Direct near jumps are only available with relative displacement, not absolute targets. You need an indirect jump for a near jump to an absolute address if you don't know your own address to calculate a relative displacement.)

    That's not even an option in 64-bit mode, where there's no jmp far 80-bit immediate ptr16:64 encoding, only memory-indirect. So you'd use mov rax, imm64 / jmp rax like a normal person if the jump target is too far away for a rel32 encoding.


    All user-space processes on Linux use the same 32-bit or 64-bit CS segment selector (with Current Privilege Level CPL = 3 = ring 3 user mode), and the kernel uses a different one (CPL=0 = ring 0 kernel mode).

    The only purpose of CS on modern x86 OSes is to select 32 vs. 64-bit mode (the .L bit in the GDT entry), and the privilege level.

    You only switch between user and kernel CS via interrupts / exceptions and instructions like int, sysenter or syscall to enter kernel mode, and iret to restore cs:eip or cs:rip from the kernel stack, or sysexit (32-bit kernels) or sysret for optimized return to user-space from system calls. After entering protected mode in the first place (with a jmp far), the kernel won't jmp far to change CS.


    Unless you want to do unstable silly computer tricks like changing to 32-bit mode in a process that started as 64-bit, there's zero reason to jmp far under Linux.

    That is possible, but I don't know if it's actually stable. e.g. the kernel might remember that your process is supposed to be 64-bit and return from an interrupt in 64-bit mode. (i.e. asynchronously set CS to the hard-coded USER32_CS constant, instead of restoring the old value.) IIRC, it does this in the syscall return path that uses sysret, see What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?

    Do you want to do that? No, you do not. There's zero support for doing so from any toolchain except for assemblers with BITS 32 vs. BITS 64 directives, basically zero benefit, and big risk of crashing (your process, not the machine). Anything you could do in hand-written asm in 32-bit mode, you could do just as well in 64-bit mode using 32-bit pointers allocated with mmap(MAP_32BIT), or using the x32 ABI.

    I guess maybe on original Core 2 (where cmp/jcc macro-fusion only works in 32-bit mode), there could be a perf advantage to running a loop in 32-bit mode and only using 64-bit mode for touching lots of memory, but switching basically costs a pipeline flush so it would normally be cheaper to just unroll a bit, instead of switching to 32-bit mode and back to 64 for a specific long-running loop.

提交回复
热议问题