3

I'm trying to get yasm to output a 16-bit near relative jmp. Specifically, it would be a rel16/rel32 jmp opcode with an operand size override prefix. I know jmp short label will emit an 8-bit near relative jmp, and a jmp long label will emit a 32-bit near relative jmp, but how do I get it to emit a 16-bit near relative jmp?

And specifically I'm using bits 32 and cpu i686

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Earlz
  • 62,085
  • 98
  • 303
  • 499

1 Answers1

5

Beware that this will truncate EIP to 16-bit IP.

The YASM/NASM syntax is jmp word label. Tested with both.

YASM also incorrectly allows it in 64-bit mode1, but NASM only allows it in 32 and 16-bit modes.


Your choices in 32-bit code (default address/operand-size = 32) are:

  • normal 2-byte jmp/jcc rel8. Enforce with jmp short
  • normal 5-byte jmp rel32 or 6-byte jcc rel32 (2-byte opcode + 4-byte rel32). jmp dword in any mode, and jmp near in 32/64-bit code.
  • operand-size prefix for 4-byte jmp rel16 / 5-byte jcc rel16 which zeros the upper 16 bits of EIP. (Not encodeable in 64-bit mode, only 16 or 32-bit mode.)
    jmp word in 16/32-bit mode, jmp near in 16-bit mode.

strict is optional, e.g. jmp strict short foo, in all of these overrides. Even without strict, it's an error not just a warning if a rel8 can't reach for both NASM and YASM. My examples also used jmp, but work with ja, jle, or any other jcc. Note that call rel8 doesn't exist, only rel16 and rel32 (and indirect), using the same override syntax.

From the Operation section of Intel's jmp documentation:

# near jump

   ...  EIP <- EIP + DEST   for non-64-bit mode relative jumps

   IF OperandSize = 32
        THEN
             EIP ← tempEIP;                         # in 64-bit mode, this truncates RIP to EIP
        ELSE
             IF OperandSize = 16
                     THEN (* OperandSize = 16 *)
                             EIP ← tempEIP AND 0000FFFFH;     #### This line
                     ELSE (* OperandSize = 64)
                             RIP ← tempRIP;
             FI;
   FI;

So you can't usefully save 1 byte by adding an operand-size prefix to get a rel16, unless your code is executing from the low 64kiB of virtual address space. (Or with a non-zero CS base, with IP=EIP.)

Just for fun, I verified that this is a real thing on my Skylake CPU: in a 32-bit Linux static executable, single-stepping 0x8049000 <foo> jmpw 0x9000 in GDB gives Cannot access memory at address 0x9000. Binutils objdump disassembles the instruction as:

# objdump -d -Mintel output from a 32-bit ELF executable
08049000 <foo>:
 8049000:       66 e9 fc ff             jmpw   9000 <foo-0x8040000>

So real execution matches this disassembly, truncating EIP to IP.


Footnote 1: In 64-bit mode: YASM and binutils bugs vs. real CPU

YASM incorrectly allows it in 64-bit mode, and GNU Binutils incorrectly decodes it as jmp rel16 in 64-bit mode. NASM correctly rejects jmp word in 64-bit mode.

But actually running it (on a Skylake) decodes it as jmp rel32, as documented by Intel. (The rel16 encoding is marked N.E. Not Encodeable in long mode).

e.g.

   foo: jmp word foo
   db   1, 1, 1, 1

assembled + linked into a Linux static executable disassembles like this with GNU Binutils 2.31.1 objdump:

0000000000401000 <foo>:
  401000:       66 e9 fc ff             jmp    1000 <foo-0x400000>
  401004:       01 01                   add    DWORD PTR [rcx],eax
  401006:       01 01                   add    DWORD PTR [rcx],eax

Actually running it in GDB (starti / si) shows that we fault on code-fetch from 0x1421002, i.e. from 0x401004 + 0x0101fffc.

That's consistent with (66 e9 fc ff 01 01) ignoring the meaningless operand-size prefix and decoding as a jmp +0x0101fffc.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Ah seems strange that you use `jmp word foo` whereas `jmp byte foo` would give an error. Regardless, good to know about the EIP truncation for rel16. My reading of that doc was that absolute targets would truncate, but not relative targets. Honestly though probably have to do some tests. Seems like with an `org 0x10000` that yasm should give an error or warning about ever using a rel16 like this – Earlz Jul 25 '19 at 19:13
  • @Earlz: The block of pseudocode that applies truncation is after and separate from the `if abs else rel` block. Just for fun, I tested to see what the tools would say (and to make double sure that my Skylake matched the documentation) – Peter Cordes Jul 25 '19 at 19:39