I'm creating my own "toy" OS, and I've gotten to the point where I am trying to understand linking and executable formats - in particular I have a flat file binary format executable which I am loading into memory at address 0x500
and then directly calling. As an example consider the following two instructions (I know its contrived, I just wanted to include both a call
and mov
in my sample)
mov ax, some_label
call some_label
; some_label is at address 0x99 into the resulting binary
Up until now I have been using NASM to produce the desired output by using the org 0x500
directive with the command nasm -f bin myfile.asm
. The resulting disassembly looks like this and works perfectly:
mov ax, 0x599
call 0x599
I now want to start using LD so that I can link against other objects, but after a lot of experimentation and reading up I still don't really understand whats going on enough to get reliable results.
I've gather than in order to produce similar output I need to:
.text
section as 0x500
and then emit the result as a flat binary - it is the linker that ultimately decides what the various offsets get resolved to in the end binary.So far I've tried the following:
:: Output as ELF
nasm -f elf myfile.asm
:: Then link and output as binary with the address of .text as 0x500
ld --oformat binary -Ttext 0x500 myfile.o
However this gives me the following error (this is on Mingw):
ld: cannot perform PE operations on non PE output file
Googling that led me to this mailing list, which seems to make sense and so instead I tried the following:
:: Output as ELF
nasm -f elf myfile.asm -o myfile.o
:: Link using LD
ld myfile.o -Ttext 0x500 -s -o myfile.tmp
:: Use objdump to output as a flat binary
objcopy -O binary myfile.tmp myfile
However the resulting myfile
looks like garbage:
00000000 66B8C105E8B8 mov eax,0xb8e805c1
00000006 0000 add [bx+si],al
I've tried a few variations on the above, but none of them produce what I was expecting and so right now I'm fairly confused:
So I figured out that I was doing multiple wrong things, the most serious of which was attempting to compile and link 16 bit code using LD (which does not support 16 bit code) and without explicitly specifying that the code was 16 bit using BITS 16
(which meant that NASM emitted 32 bit memory addresses).
This overall accounted for some of the odd error messages that LD would give me in some variations of the above situation, as well as why the dis assembly of the linked binary was garbage - I was attempting to decompile 32 bit code as 16 bit code or visa versa.
Ideally I'd like to be able to link 16 bit code, but having discovered that I can't do this with LD (and there are very few alternatives that can do this) I decided to settle with understanding whats going on when I repeat the same thing with 32 bit code. My input file:
; Address locations are now 32 bits so I must use `eax` instead of `ax`
mov eax, some_label
call some_label
; some_label is at a different address into the resulting binary (because
; pointers are wider the resulting machine code is larger)
I then link this using the following:
:: Output as win32 object files - makes it possible to use -r with LD.
nasm -f win myfile.asm -o myfile.o
:: Link using LD - the -r flag prevents extra un-used code (e.g. __CTOR_LIST__) being generated - see the link in my original question text
ld myfile.o -Ttext 0x500 -s -r -o myfile.tmp
:: Use objdump to output as a flat binary - -j .text ensures that just the .text section is included which keeps the output file size down
objcopy -O binary -j .text myfile.tmp myfile
When disassembling using ndisasm
I need to specify -b 32
in order to get it to correctly interpret the code as 32 bit.
It took a lot of experimentation and reading up of obscure forum links but I now understand the whole thing a lot better as a result.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With