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.
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