7

How can I write a far absolute JMP or CALL instruction using MASM? Specifically how do I get it to emit these instruction using the EA and CA opcodes, without manually emitting them using DB or other data directives?

For example consider the case of jumping to the BIOS reset entry point at FFFF:0000 in a boot sector. If I were using NASM I could code this in one instruction in the obvious way:

jmp 0xffff:0

With the GNU assembler the syntax is less obvious, but the following will do the job:

jmp 0xffff, 0

However when I try the obvious solution with MASM:

jmp 0ffffh:0

I get the following error:

t206b.asm(3) : error A2096:segment, group, or segment register expected

Workarounds I'm trying to avoid

There are a number of possible workarounds I could use in MASM, like any of the following:

Hand assemble the instruction, emitting the machine code manually:

    DB 0EAh, 0, 0, 0FFh, 0FFh

Use a far indirect jump:

bios_reset DD 0ffff0000h
    ...
    jmp bios_reset   ; FF 2E opcode: indirect far jump

Or push the address on the stack and use a far RET instruction to "return" to it:

    push 0ffffh
    push 0
    retf

But is there anyway I can use an actual JMP instruction and have MASM generate the right opcode (EA)?

Ross Ridge
  • 38,414
  • 7
  • 81
  • 112
  • https://sourceware.org/binutils/docs/as/i386_002dVariations.html says that `jmp far section:offset` should do it. Is that only NASM, not MASM? I'm guessing it doesn't work for MASM or you prob. would have found it. >. – Peter Cordes Sep 22 '15 at 02:39
  • @PeterCordes I don't know if any assembler accepts that syntax. Even NASM rejects it, it doesn't like the `far` keyword being there. – Ross Ridge Sep 22 '15 at 02:53
  • Hmm. I thought `far jmp` or `jmp far` was a thing. I consider myself lucky never to have had to write code that will run with a segmented memory model, though, so IDK. Maybe only the GNU assembler in intel-syntax mode accepts that syntax, since that page is from the docs for GNU binutils. – Peter Cordes Sep 22 '15 at 02:55
  • 1
    @PeterCordes The GNU assembler using Intel syntax doesn't like the `far` keyword being there either. Where the `far` keyword does come in handy is with indirect jumps. With far absolute jumps the format of the operand `segment:offset` indicates that its a far jump, a near (relative) jump is just `jmp label`. But with a far indirect jump, eg. `jmp far [0x123]` or `jmp far [eax]`, the far keyword is necessary to distinguish it from a near indirect jump. (Note that this is just for GAS/NASM. MASM uses the size of the memory operand to distinguish between the two.) – Ross Ridge Sep 22 '15 at 03:20
  • 1
    I have a suspicion it is probably a typo in the doc. I've used GAS/Intel but using the `jmp far ptr segment:offset` syntax with `.code16` . Possibly they just left off `ptr` – Michael Petch Sep 22 '15 at 03:32
  • I thought I may have seen the `jmp far segment:offset` in my lifetime and discovered it is accepted on Turbo Assembler. I wonder what the Intel C Compiler would accept (I don't have it installed to find out) – Michael Petch Sep 22 '15 at 04:00
  • @MichaelPetch Hmm... TASM 3.1 and 5.0 both reject 'jmp far 0ffffh:0` for me, complaining about an "illegal immediate". – Ross Ridge Sep 22 '15 at 04:31
  • I happened to take my Turbo C 2.0 manual (with TASM 1.0) off the shelf, and the part I forgot to mention was that this was in TASM's IDEAL mode. One difference was the extra form of jmp (`jmp far 0f000h:0fff0h`) that IDEAL mode supported that MASM mode didn't. I tried a simple test with TASM 3 IDEAL and it did work. It did fail with the error you received in MASM mode. – Michael Petch Sep 22 '15 at 05:23
  • 1
    Yah, I just checked it myself. Both TASM 3.1 and 5.0 accept it in Ideal mode. – Ross Ridge Sep 22 '15 at 05:26

4 Answers4

6

There's one way you can do it, but you need to use MASM's /omf switch so that it generates object files in the OMF format. This means the object files need to be linked with an OMF compatible linker, like Microsoft's old segmented linker (and not their current 32-bit linker.)

To do it you need to use a rarely used and not well understood feature of MASM, the SEGMENT directive's AT address attribute. The AT attribute tells the linker that the segment lives at a fixed paragraph address in memory, as given by address. It also tells the linker to discard the segment, meaning the contents of the segment aren't used, just its labels. This is also why the /omf switch has to be used. MASM's default object file format, PECOFF, doesn't support this.

The AT attribute gives you the segment part of the address we want to jump to. To get the offset part all you need to do is use the LABEL directive inside the segment. Combined with the ORG directive, this lets you create a label at the specific offset in the specific segment. All you then need to do is use this label in the JMP or CALL instruction.

For example if you want to jump to the BIOS reset routine you can do this:

bios_reset_seg SEGMENT USE16 AT 0ffffh
bios_reset LABEL FAR
bios_reset_seg ENDS

_TEXT SEGMENT USE16 'CODE'
    jmp bios_reset
_TEXT ENDS

Or if you want to call the second stage part of your boot loader whose entry point is at 0000:7E00:

zero_seg SEGMENT USE16 AT 0
    ORG 7e00h
second_stage LABEL FAR
zero_seg ENDS

_TEXT SEGMENT USE16 'CODE'
    call second_stage
_TEXT ENDS
Sep Roland
  • 33,889
  • 7
  • 43
  • 76
Ross Ridge
  • 38,414
  • 7
  • 81
  • 112
  • 1
    Although this is entirely true (thus the upvote) it is generally far easier to code it directly with byte directives and stuff it in a MASM macro to hide the gory details. Probably a coincidence but there was a NASM question today with similar JMPs and used 0x7E00 ;-) – Michael Petch Sep 22 '15 at 01:38
  • 1
    I try to avoid macros like that because they hide the gory details. This is a problem I encountered and figured out the solution to a few days ago, but yes, posting this question was prompted by a couple of recently active questions here. – Ross Ridge Sep 22 '15 at 02:29
0

Have you considered using the __emit pseudoinstruction?

https://msdn.microsoft.com/en-us/library/1b80826t.aspx

I once had to use inline assembler to code a far jump. I can't remember the opcode for a far jump, but you could do something like this

__emit      0eah
__emit      0
__emit      0
__emit      0
__emit      0
__emit      8h
__emit      0
Bran
  • 617
  • 8
  • 21
  • 2
    The `__emit` keyword doesn't work in MASM, it's specific to Visual C++ inline assembly. In MASM you'd use the `DB` directive to do this, but as I mentioned in my question, I'm looking for a solution that doesn't require a workaround like this. I'm looking for something that would let me use the actual `JMP` mnemonic to get the far absolute jump instruction. – Ross Ridge Jun 29 '16 at 15:53
0

It is not possible to do in MASM. See the article in the following link:

https://www.betaarchive.com/wiki/index.php?title=Microsoft_KB_Archive/24965

  • 3
    There is an existing answer which shows how to do it, with limitations. If there's something specific that's relevant in that link, please quote or summarize it. Normally, link-only answers aren't a good fit for Stack Overflow (because links can rot). Apart from saying it's impossible (which Ross's existing answer says in more detail, that it's not possible with the normal 32-bit linker), your answer doesn't add anything new except the link. That could just be a comment. – Peter Cordes Jan 04 '23 at 14:08
-1

i have been testing with MASM 6.15 this code, i think this can help you.

    .model small
    .386
    .stack 100h

    .DATA
     Message    DB  'hello','$'
     JMPPOS DB  78h,56h,34h,12h  ;the address to be jumped to  1234:5678

    .code
    .startup

     JMP  dword  ptr [JMPPOS]

    .exit
    END
basm
  • 1
  • 2
  • 4
    This doesn't really answer the question. This code uses an indirect far jump not an far absolute jump. I already pointed out that an indirect jump could be used as a workaround in my question, but I'm looking for way to get MASM to generate the `EA` opcode with the JMP instruction. – Ross Ridge Jun 10 '16 at 18:35