ld using physical address for symbol, instead of relative

Programming, for all ages and all languages.
Post Reply
zman97211
Posts: 22
Joined: Sun Aug 23, 2009 7:35 am

ld using physical address for symbol, instead of relative

Post by zman97211 »

I've managed to find a workaround for this problem, but I'd rather have a solution. I'm writing this from work so I don't have my exact code handy.

My basic kernel (not really a kernel) is leaded by Grub, which sets up a basic GDT for a flat memory model, loads my "kernel", and begins executing it. In my code, I establish another basic GDT, load it with lgdt, then execute a far jump to set up my new CS.

Code: Select all

lgdt   my_gdt     ; This is the pointer to the new gdt
jmp   CS_SEL:0  ; make the jump (CS_SEL is a segment selector)

section MY_SECTION
main:
     mov   ds,DS_SEL
     ;etc....
I have MY_SECTION separate so that I can have my code segment set up starting at that point. The above code works great, except I would love to be able to write "jmp CS_SEL:main" and allow the linker to figure things out. However, the linker assigns main 0x300000, which is the location of my segment in physical memory, but really I would prefer main to be zero.

Perhaps this can't be done. I could have sworn I've read something in the binutils ld manual about this, but I can't seem to find it, or anything else about this on the net.
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: ld using physical address for symbol, instead of relative

Post by Combuster »

You can tell ld to use a different load and virtual address. There's a sample linker script on the Higher Half bare bones page, which can inspire you to achieve what you want
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
zman97211
Posts: 22
Joined: Sun Aug 23, 2009 7:35 am

Re: ld using physical address for symbol, instead of relative

Post by zman97211 »

I'm not sure if either I'm not understanding what is on that page, or if you misunderstood my problem.

My code is properly getting loaded at the 3MB point in memory, and I am able to jump to it and run it successfully using "jmp CS_SEL:0", which happens to be the instruction immediately following the symbol "main" in my assembly source. "main" is the first symbol pointing to the first instruction appearing in that segment (meaning both the segment defined within my assembly source, and the segment as defined in the GDT I set up). I would instead like to use "jmp CS_SEL:main".

The segment in the GDT that CS_SEL refers to is at base address 0x300000, with a limit of 4k or so. "main" is at offset zero within that segment. If I attempt to execute "jmp CS_SEL:main", I get a protection exception due to the offset 0x300000 being outside the limit of the segment.

I want ld to "know" that anywhere I refer to the symbol "main", it should return an offset of zero.

I've played around with various settings of VMA and LMA (as the ld documentation calls them) in my linker script to no avail. The Intel architecture programming manuals only say that a far jump is required to get the new CS set up due to the fact that there are a limited number of ways of accessing CS and EIP (the other method is utilizing the stack to pop new values into them). I think this is a problem with using my linker properly, since it determines what "main" is.

I shouldn't need to enable paging to do what I want. Assuming it's even possible.

From what I read on the the page you linked, that loader uses some fancy paging to get loaded in a different location than Grub would normally be able to load a kernel into. Grub has no problems getting my kernel where I asked it to.

Please help me if I'm missing something.

Steve
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: ld using physical address for symbol, instead of relative

Post by Combuster »

What that linker script does is
"load everything at 0x100000, but make all addresses as if it were at 0xC0100000"
The code then creates pages, which makes the virtual address C0100000 point to 00100000

What would happen if you changed that script so that it reads:
"load everything at 0x300000, but make all addresses as if it started at 0x00000000"

the only problem you'd have is that main must be the very first thing in the file/section to make it actually appear at location 0.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
zman97211
Posts: 22
Joined: Sun Aug 23, 2009 7:35 am

Re: ld using physical address for symbol, instead of relative

Post by zman97211 »

Here is my source:

Code: Select all

[BITS 32]

%include	"gdtn_inc.asm"

section	s_multiboot
ALIGN 4
mboot:
	; Multiboot macros to make a few lines later more readable
	MULTIBOOT_PAGE_ALIGN	equ 1<<0
	MULTIBOOT_MEMORY_INFO	equ 1<<1
	MULTIBOOT_AOUT_KLUDGE	equ 1<<16
	MULTIBOOT_HEADER_MAGIC	equ 0x1BADB002
	MULTIBOOT_HEADER_FLAGS	equ MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO | MULTIBOOT_AOUT_KLUDGE
	MULTIBOOT_CHECKSUM	equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)
	EXTERN code, bss, end
	; This is the GRUB Multiboot header. A boot signature
	dd	MULTIBOOT_HEADER_MAGIC
	dd	MULTIBOOT_HEADER_FLAGS
	dd	MULTIBOOT_CHECKSUM

	; AOUT kludge - must be physical addresses. Make a note of these:
	; The linker script fills in the data for these ones!
	;dd	mboot
	;dd	code
	;dd	bss
	;dd	end
	;dd	_loader

section	s_loader
global	_loader
_loader:
	cli
	lgdt	[gdtloc]
	jmp	CS_SEL:0

SECTION	s_code
main:
	mov	eax,VID_SEL
	mov	ds,eax
	mov	ebx,0x0
	mov	ecx,80*25
.starloop:
	mov	[ebx],byte 0x0f
	mov	[ebx+1],byte '*'
	add	ebx,2
	dec	ecx
	jnz	.starloop
	jmp	$

SECTION	s_gdt
gdtloc:
	start_gdt
	CS_SEL	desc 0x300000,0x1000,D_CODE + D_DPL0 + D_BIG
	DS_SEL	desc 0x200000,0x1000,D_DATA + D_DPL0 + D_BIG + D_WRITE
	VID_SEL	desc 0xB8000,0xf9f,D_DATA + D_DPL0 + D_BIG + D_WRITE
	end_gdt
Here is my link script:

Code: Select all

OUTPUT_ARCH(i386)
OUTPUT_FORMAT(elf32-i386)
INPUT(kernel.o)
OUTPUT(kernel.bin)
ENTRY(_loader)
phys = 0x00100000;
SECTIONS
{
	. = 0x100000;
	s_multiboot :
	{
		*(s_multiboot)
		. = ALIGN(4096);
	}
	s_loader :
	{
		*(s_loader)
		. = ALIGN(4096);
	}
	s_gdt 0x200000 :
	{
		*(s_gdt)
		. = ALIGN(4096);
	}
	s_code 0x300000 :
	{
		*(s_code)
		. = ALIGN(4096);
	}
}
All I simply want to do is change the "jmp CS_SEL:0" to "jmp CS_SEL:main" in the event that main is NOT the first few bytes of the s_code segment.

I suppose I should simply ensure that main is at the beginning of the s_code segment in the final output file and always just jump to CS_SEL:0.

It's easy enough to do that. I don't want to add the clutter (at this point) of enabling paging, even if temporarily.

FYI - I chose the segment names because I'm staying away from interfacing with C at this point, and I feel that .text/.bss etc are not very readable. The locations of the segments are completely arbitrary to allow me to easily see what is happening when debugging with bochs.

Steve
zman97211
Posts: 22
Joined: Sun Aug 23, 2009 7:35 am

Re: ld using physical address for symbol, instead of relative

Post by zman97211 »

Oh! I get it! :D

So after getting to the point that I really needed to implement some sort of memory management, I eventually started reading about paging. I then went back to Higher Half Bare Bones and thought about it quite a bit... My next task is to write a simple do-nothing kernel just to fix what I wrote about above to make sure I understand completely.

But I do have a question, and it's more of a verification I'm looking for from you more experienced folks.

It should be possible to do the higher-half kernel using 4k pages instead of the 4MB page, without setting the PSE bit of CR4, as long as I map enough 4k pages to encompass my entire kernel. Correct?

Also - the way the higher-half bare bones is written, the video buffer would appear at 0xC00B8000, right?

I could leave the first meg of memory identity mapped (instead of unmapping it in the example) and still access it (and probably the multiboot information if the bootloader doesn't put it above 0x100000) as I normally would (e.g. 0xb8000)?

However, applications written for an OS that does this would need to be aware of it - The first meg is off-limits for their use.

Unless... each application or process would need its own page directory/table in order to separate it's memory from other processes. That other page directory/table could have other pages of physical memory mapped to the first meg, so then they would be able to access it as if it were their own without conflicting with video RAM and other ROM stuff. ?

These other processes' page directory/table would need to have at least some pages mapped to the kernel to be able to call kernel APIs and such. Which is now why I think task gates are probably useful, to switch tasks (and the page tables in use) to the kernel's... Is this correct also?

Would the IDT also need to have task gates (or the equivalent) when an exception is generated that would require a switch to the kernel's code, if the kernel weren't mapped into the applications memory space?

I'm now assuming that the best implementation (or at least the easiest) is to have the kernel's memory space mapped into the application's memory space to avoid all this confusion, at least to begin with.

Anyway... I've used this message to kind of organize my own thoughts on the subject, and I would like feedback on where I may be right or wrong.

Thanks!

Steve
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: ld using physical address for symbol, instead of relative

Post by Combuster »

It should be possible to do the higher-half kernel using 4k pages instead of the 4MB page, without setting the PSE bit of CR4, as long as I map enough 4k pages to encompass my entire kernel. Correct?
You'd have to replace the large page with a page table which in turn contains enough entries to encompass everything
Also - the way the higher-half bare bones is written, the video buffer would appear at 0xC00B8000, right?
There, and unless you wrote code to remove the other mapping, 0xB8000 will work too (see the comment at BootPageDirectory)
I could leave the first meg of memory identity mapped (instead of unmapping it in the example) and still access it (and probably the multiboot information if the bootloader doesn't put it above 0x100000) as I normally would (e.g. 0xb8000)?
True
However, applications written for an OS that does this would need to be aware of it - The first meg is off-limits for their use.
You don't need to have a mapping in all address spaces. Also, consider the fact that Virtual 8086 mode will always be run in the first 1 MB. Besides with paging enabled it is just as easy to map things like video memory to a location in the higher half first. (solving the problem altogether)
Unless... each application or process would need its own page directory/table in order to separate it's memory from other processes. That other page directory/table could have other pages of physical memory mapped to the first meg, so then they would be able to access it as if it were their own without conflicting with video RAM and other ROM stuff. ?
directory + tables :wink:
These other processes' page directory/table would need to have at least some pages mapped to the kernel to be able to call kernel APIs and such. Which is now why I think task gates are probably useful, to switch tasks (and the page tables in use) to the kernel's... Is this correct also?
Task gates are slow, and they (as do all other CPU structures) are still required to be in memory, essentially defeating the idea of having no kernel present in all directories.
Would the IDT also need to have task gates (or the equivalent) when an exception is generated that would require a switch to the kernel's code, if the kernel weren't mapped into the applications memory space?

I'm now assuming that the best implementation (or at least the easiest) is to have the kernel's memory space mapped into the application's memory space to avoid all this confusion, at least to begin with.
True and true.
Thanks!
You're welcome :)
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
zman97211
Posts: 22
Joined: Sun Aug 23, 2009 7:35 am

Re: ld using physical address for symbol, instead of relative

Post by zman97211 »

Good to hear I have a grasp on the subject.

Thanks for your reply!
zman97211
Posts: 22
Joined: Sun Aug 23, 2009 7:35 am

Re: ld using physical address for symbol, instead of relative

Post by zman97211 »

This allows the jump to CS_SEL:main. The key is the linker script.

Main isn't required to be at offset zero in the segment. The kernel is loaded at 0x10000, and all symbols are offset from this base (so if main is loaded at 0x101000, a jump to CS_SEL:main really will jump to CS_SEL:0x1000, where CS_SEL is a segment with a base of 0x10000).

Thanks go to the author of this page for the macros used to build the GDT.

Linker script:

Code: Select all

OUTPUT_ARCH(i386)
OUTPUT_FORMAT(elf32-i386)
INPUT(loader.o gdt.o)
OUTPUT(kernel.bin)
ENTRY(loader)
SECTIONS
{
	. = 0;
	.text : AT(ADDR(.text) + 0x100000)
	{
		loader.o(.text)
		*(.text)
		*(.rodata)
		. = ALIGN(4096);
	}
	.data : AT(ADDR(.data) + 0x100000)
	{
		*(.data)
		. = ALIGN(4096);
	}
	.bss : AT(ADDR(.bss) + 0x100000)
	{
		*(.bss)
		. = ALIGN(4096);
	}
}
Loader.asm:

Code: Select all

[BITS 32]

global loader

section .text

ALIGN 4
mboot:
    MULTIBOOT_PAGE_ALIGN	equ 1<<0
    MULTIBOOT_MEMORY_INFO	equ 1<<1
    MULTIBOOT_AOUT_KLUDGE	equ 1<<16
    MULTIBOOT_HEADER_MAGIC	equ 0x1BADB002
    MULTIBOOT_HEADER_FLAGS	equ MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO
    MULTIBOOT_CHECKSUM	equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)

    dd MULTIBOOT_HEADER_MAGIC
    dd MULTIBOOT_HEADER_FLAGS
    dd MULTIBOOT_CHECKSUM

extern gdtloc
extern CS_SEL

loader:
	lgdt	[gdtloc+0x100000]
	jmp	CS_SEL:main

main:
    mov esp, _sys_stack
    jmp $

SECTION .bss
    resb 8192               ; This reserves 8KBytes of memory here
_sys_stack:
gdt.asm:

Code: Select all

%include	"gdtn_inc.asm"

global	STACK_TOP
global	CS_SEL
global	DS_SEL
global	SS_SEL
global	VID_SEL
global	gdtloc

STACK_TOP	equ	0x10000

SECTION .data
gdtloc:
	start_gdt
	CS_SEL	desc 0x100000,0x80000,D_CODE + D_DPL0 + D_BIG
	DS_SEL	desc 0x100000,0x80000,D_DATA + D_DPL0 + D_BIG + D_WRITE
	end_gdt
[Edit - I changed the lgdt line in the assembly source to include the offset to the GDT]
Post Reply