Search code examples
assemblydosmasm

MASM: Force OFFSET directive not to be relocated


I'm writing a multi-modular assembly project with MASM for DOS. In particular, I'm trying to do a very simple DOS-like OS,

I have several segments defined like this;

fileseg segment byte public 'code'
fileseg ends

memseg segment byte public 'code'
memseg ends

diskseg segment byte public 'code'
diskseg ends

bootseg segment byte public 'code'
bootseg ends

As you see all of them are byte-aligned (design decision). Also I have a cseg group that encompasses all of the above segments

cseg group fileseg, memseg, diskseg, bootseg, ...

so when I want to reference a public label, no matter if it's defined in a different segment, i will do something like:

assume ds:cseg, es:cseg, ss:cseg

mov   si, offset label_in_this_segment
mov   bx, offset label_in_another_segment

and it'll get the 'global' offset within cseg, which is right. But the problem arises when you write some code that needs to be executed in a fixed location, as a boot sector, that executes at 0000:7C00h (in my case at 07C0:0)

so I wrote it like this:

bootseg segment byte public 'code'

; Want all data references to be relative to this segment, not CSEG
assume ds:bootseg

boot_start::

    jmp     start

    boot_data   t_bootloader_data <>        

start:

    ; make IP point to 0000h
    mov     ax, 07C0h
    push    ax
    mov     ax, offset ds:real_start ; "BAD" offset
    push    ax
    retf
real_start:        
    mov     ax, cs
    mov     ds, ax
    ...

I put "BAD" in quotes because MASM is actually doing it right, since bootseg is also part of the cseg group (because boot_data it's referenced from some other segments), so the linker marks "real_start" as relocatable.

So the question would be: how do I tell the assembler that I wantreal_start to be offseted from bootseg itself?

I tried some tricks that work, like making bootseg be the first segment defined, or making it paragraph-aligned, but these seem somewhat hacky to me.

I also tried to put bootseg out of the cseg group but again, I need boot_data to be referenced from other segments before the boot sector is written to disk.


Solution

  • You can specify what segment you want OFFSET to be relative by prefixing the symbol with segment:. For example:

         mov     ax, offset bootseg:real_start
    

    However this doesn't work because you're using BYTE alignment, so the there's two different starting address for the segment. In real mode segments have to be paragraph (16-byte) aligned, so linker rounds down the start bootseg to the nearest paragraph boundary, and then adjusts any offsets to be relative to the new starting address. Since when the bootseg is actually loaded and executes it does actually start a paragraph boundry this means all the adjusted offsets are incorrect.

    Note that is isn't limited to just the OFFSET directive, but also any absolute references bootseg makes to addresses within bootseg. You should really consider making bootseg PARA aligned.

    If you still want to use byte alignment you'll need to calculate the offset yourself:

         mov     ax, real_start - boot_start
    

    Note that since you're targeting 80286 processors, you can do this instead:

         push    real_start - boot_start
    

    The PUSH immediate instruction was introduced to the x86 architecture with the 80186.

    If you have absolute references like this within bootseg:

    mov  al, [boot_data.foo]
    

    You'll need to change it to something like this for the offset to be correct when the boot sector is executed:

    mov  al, BYTE PTR ds:[boot_data.foo - boot_start]
    

    Finally you can avoid the whole PUSH/RETF nonsense and code an absolute far JMP to a fixed segment directly in MASM like this:

    bootseg segment byte public 'code'
    
    assume ds:bootseg
    
    boot_start::
    
        jmp     start
    
        ; ...
    
    start:
    
        jmp     real_start_abs
    real_start:        
        mov     ax, cs
        mov     ds, ax
    
        ; ...
    
    bootseg ENDS
    
    bootseg_abs SEGMENT USE16 AT 07c0h
        ORG      (real_start - boot_start)
    real_start_abs LABEL FAR
    bootseg_abs ENDS