Mdd M
Mdd M

Reputation: 321

Incorrect Relative call address for 32/16bit bootloader compiled using gcc/ld for x86

This question is similar to Incorrect relative address using GNU LD w/ 16-bit x86, but I could not solve by building a cross-compiler.

The scenario is, I have a second stage bootloader that starts as 16bit, and brings itself up to 32 bit. As a result, I have mixed assembly and C code with 32 and 16 bit code mixed together.

I have included an assembly file which defines a global that I will call from C, basically with the purpose of dropping back to REAL mode to perform BIOS interrupts from the protected mode C environment on demand. So far, the function doesn't do anything except get called, push and pop some registers, and return:

[bits 32]
BIOS_Interrupt:
PUSHF
...
ret
global BIOS_Interrupt

this is included in my main bootloader.asm file that is loaded by the stage 1 mbr.

In C, I have defined:

extern void BIOS_Interrupt(uint32_t intno, uint32_t ax, uint32_t bx, uint32_t cx, uint32_t dx, uint32_t es, uint32_t ds, uint32_t di, uint32_t si);

in a header, and

BIOS_Interrupt(0x15,0,0,0,0,0,0,0,0);

in code, just to test calling

I can see in the resultant disassembled linked binary that the call is invariably set 2 bytes too low in RAM:

00000132  0100              add [bx+si],ax
00000134  009CFA9D          add [si-0x6206],bl
00000138  6650              push eax
0000013A  6653              push ebx
...
00001625  6A00              push byte +0x0
00001627  6A00              push byte +0x0
00001629  6A00              push byte +0x0
0000162B  6A00              push byte +0x0
0000162D  6A00              push byte +0x0
0000162F  6A00              push byte +0x0
00001631  6A00              push byte +0x0
00001633  6A00              push byte +0x0
00001635  6A15              push byte +0x15
00001637  E8F9EA            call 0x133

The instruction at 135 should be the first instruction reached (0x9C = PUSHF), but the call is for 2 bytes less in memory at 133, causing runtime errors.

I have noticed that by using the NASM .align keyword, the extra NOPs that are generated do compensate for the incorrect relative address.

Is this an issue with the linking process? I have LD running with -melf_i386, NASM with -f elf and GCC with -m32 -ffreestanding -0s -fno-pie -nostdlib

edit: images added for @MichaelPetch. Code is loaded at 0x9000 by MBR. Interestingly, the call shows a correct relative jump, to 0x135, but the executing disassembly at 0x135 looks like the code at 0x133 (0x00, 0x00).

Bochs about to call BIOS_Interrupt
screenshot of Bochs about to call BIOS_Interrupt

Bochs at call start
screenshot of Bochs at call start

edit 2: correction to image 2 after refreshing memdump after call

memdump and dissasembly after calling BIOS_Interrupt (call 0x135)

Upvotes: 1

Views: 146

Answers (1)

Mdd M
Mdd M

Reputation: 321

Thanks again to @MichaelPetch for giving me a few pointers.

I don't think there is an issue with the linker, and that the dissassembly was "tricking" me, in that the combination of 16 and 32 bit code led to inaccurate code.

In the end, it was due to overriding of memory values from prior operations. In the code immediately before the BIOS_Interrupt label, I had defined a dword, dd IDT_REAL, designed to store the IDT for real mode processing. However, I did not realise (or forgot) that the SIDT/LIDT instructions take 6 bytes of data, so when I was calling SIDT, it was overriding the first 2 bytes of the label's location in RAM, resulting in runtime errors. After increasing the size of the variable from dword to qword, I can run just fine w/o error.

The linker/compiler suggestion seems to be a red-herring that I fell for courtesy of objdump. However, I've at least learned from this the benefits of Bochs and double checking code before jumping to conclusions!

Upvotes: 2

Related Questions