Trying to set up a stack segment with limits and failing

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
sboydlns
Posts: 12
Joined: Mon Feb 17, 2025 9:53 am

Trying to set up a stack segment with limits and failing

Post by sboydlns »

Up until now I have been using the GDT settings from the tutorials. That is one massive 4GB address space for the code and data segments. Now I am trying to set up a segment for the stack with some hard limits. There is clearly something that I am not understanding about all of this since I can't seem to get it to work. I'm not getting exceptions but memory is being corrupted and I can't figure out why.

My stack segment looks like this:

Code: Select all

gdt_stack:							# limit 3e00 - 07dff
	.word	0x3fff					# segment limit 0-15
	.word	0x3e00					# base 0-15
	.byte	0x0						# base 15-23
	.byte	0x92					# present, priv 0, data segment, writable
	.byte	0x40					# byte granularity, 32-bit, limit 16-19
	.byte	0						# base 24-31
I am setting %SS to 0x18 (which is the offset into my GDT where this segment descriptor is located) and I am loading %ESP with 0x3ff0. According to my understanding this should have the effect of locating my stack at 0x7df0 (0x3e00 + 0x3ff0). Yet this doesn't seem to be happening. As I said earlier, memory is being corrupted.

Setting %SS to the offset to my data segment and setting %ESP to 0x7df0 works as expected.

I am puzzled. Any help is appreciated.

TIA
User avatar
iansjack
Member
Member
Posts: 4759
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: Trying to set up a stack segment with limits and failing

Post by iansjack »

Without seeing your code it’s not easy to guess what you are doing wrong. Do you have an on-line repository?
MichaelPetch
Member
Member
Posts: 829
Joined: Fri Aug 26, 2016 1:41 pm
Libera.chat IRC: mpetch

Re: Trying to set up a stack segment with limits and failing

Post by MichaelPetch »

An observation: The 32-bit and 64-bit GCC compilers emit code that assumes that the base of SS, DS, ES, and CS are all 0. Non zero base will cause problems for emitted C code especially if you are passing the address of a stack based variable around. I recall from your previous posts you are using GCC/clang.

QEMU's software emulator doesn't do segment limit checks (for performance) so if you do have an ESP that falls outside the stack segment limits you won't get an #SS exception. Running with `--enable-kvm` may produce those #SS exceptions.
User avatar
iansjack
Member
Member
Posts: 4759
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: Trying to set up a stack segment with limits and failing

Post by iansjack »

I was assuming the code was assembly as the small fraction quoted is. But, as you say, if it’s C or C++ you have to assume a flat memory model.
sboydlns
Posts: 12
Joined: Mon Feb 17, 2025 9:53 am

Re: Trying to set up a stack segment with limits and failing

Post by sboydlns »

iansjack wrote: Sun Mar 02, 2025 2:35 pm Without seeing your code it’s not easy to guess what you are doing wrong. Do you have an on-line repository?
Unfortunately, I don't.

After a bit more poking around with the debugger I have confirmed that my stack is in the place where I expect it to be. It almost seems as though my .data section has been moved as well. So it's not so much a data corruption problem as it is the data isn't where my C code expects to find it.

May some code snippets will help.

GDT definition

Code: Select all

.align 8
gdt:
	.long	0						# system reserved entry
	.long	0

gdt_code:							# limit 0 - ffffffff
	.word	0xffff					# segment limit 0-15
	.word	0x0						# base 0-15
	.byte	0x0						# base 15-23
	.byte	0x9a					# present, priv 0, code segment, readable
	.byte	0xcf					# 4k blocks, 32-bit, limit 16-19
	.byte	0						# base 24-31

gdt_data:							# limit 0 - ffffffff
	.word	0xffff					# segment limit 0-15
	.word	0x0						# base 0-15
	.byte	0x0						# base 15-23
	.byte	0x92					# present, priv 0, data segment, writable
	.byte	0xcf					# 4k blocks, 32-bit, limit 16-19
	.byte	0						# base 24-31

gdt_stack:							# limit 3e00 - 07dff
	.word	0x3fff					# segment limit 0-15
	.word	0x3e00					# base 0-15
	.byte	0x0						# base 15-23
	.byte	0x92					# present, priv 0, data segment, writable
	.byte	0x40					# byte granularity, 32-bit, limit 16-19
	.byte	0						# base 24-31

gdt_end:
	.equ	gdt_len, gdt_end - gdt

	.equ	CODE_SEG, gdt_code - gdt
	.equ	DATA_SEG, gdt_data - gdt
	.equ	STACK_SEG, gdt_stack - gdt
Set up 32 bit mode

Code: Select all

# Copy initial GDT to low memory

	cld								# set movs to increment
	movw	$gdt,%si				# move from our copy of GDT
	movw	$GDT,%di				# to the one in low memory
	movw	$gdt_len,%cx			# for the length of our copy
	rep
	 movsb

# Enable 32-bit protected mode

	cli								# disable interrupts until we are ready
	movw	$GDT_LEN,GDT_REG		# init the GDT register with the length
	movl	$GDT,GDT_REG+2			# & the address
	lgdt	GDT_REG					# load the global descriptor table

	movl	%cr0,%eax				# set protected mode
	orl		$0x01,%eax
	movl	%eax,%cr0
	jmp		$CODE_SEG,$start		# force CS to use entry from GDT

.code32

# From this point on, we are in 32-bit protected mode.

start:
	movw	$DATA_SEG,%ax			# set data segment registers
	movw	%ax,%ds
	movw	%ax,%es
	movw	%ax,%fs
	movw	%ax,%gs

# Set up a stack
#
# GDT stack segment is configured to support a 16K kernel
# stack occupying 0x3e00 - 0x7dff

	movw	$STACK_SEG,%ax
	movw	%ax,%ss
	movl	$0x3ff0,%esp			# set bottom of stack to end of boot area
Using the debugger I have verified that DS contains 0x10 and SS contains 0x18. Changing the offset on the segment description that I am using for the stack causes everything to start working again. Hmmmm!
sboydlns
Posts: 12
Joined: Mon Feb 17, 2025 9:53 am

Re: Trying to set up a stack segment with limits and failing

Post by sboydlns »

MichaelPetch wrote: Sun Mar 02, 2025 3:16 pm An observation: The 32-bit and 64-bit GCC compilers emit code that assumes that the base of SS, DS, ES, and CS are all 0. Non zero base will cause problems for emitted C code especially if you are passing the address of a stack based variable around. I recall from your previous posts you are using GCC/clang.
That would explain it. I guess I naively assumed that using the GDT would provide a convenient way to relocate code without have to do anything except load it at the new address and modify the base in the segment descriptor.

This raises a question that isn't relevant at this point but will become so. If C assumes that segment bases are zero then how does one move .data sections around in memory if the heap needs to be expanded or whatever? I always assumed you would move the section and modify the GDT. But I'm guessing that is not the case. Possibly that has something to do with paging? Which I haven't really looked at yet.
User avatar
Demindiro
Member
Member
Posts: 111
Joined: Fri Jun 11, 2021 6:02 am
Libera.chat IRC: demindiro
Location: Belgium
Contact:

Re: Trying to set up a stack segment with limits and failing

Post by Demindiro »

sboydlns wrote: Sun Mar 02, 2025 3:48 pm This raises a question that isn't relevant at this point but will become so. If C assumes that segment bases are zero then how does one move .data sections around in memory if the heap needs to be expanded or whatever? I always assumed you would move the section and modify the GDT. But I'm guessing that is not the case. Possibly that has something to do with paging? Which I haven't really looked at yet.
The heap and .data sections are unrelated.
.data is loaded when the program is loaded (by the kernel or some helper program) and remains at a fixed place (after relocations are applied, if necessary). Generally, the program is responsible for providing its own heap. Usually libc or whatever equivalent you use is responsible for initializing the allocator.

The user-space memory allocator shouldn't have to care where exactly memory is allocated. It just needs to be able to get memory from the kernel (who in turn should know which regions are unallocated). Paging is useful here since you can leave unused ranges unmapped to save memory.

The kernel of course needs to know what is actually RAM and usable, but again that's unrelated to .data, the allocator shouldn't touch that.
My OS is Norost B (website, Github, sourcehut)
My filesystem is NRFS (Github, sourcehut)
^ defunct
rdos
Member
Member
Posts: 3351
Joined: Wed Oct 01, 2008 1:55 pm

Re: Trying to set up a stack segment with limits and failing

Post by rdos »

The simple answer is that you cannot use segmentation if you use GCC or any other "mainstream" compiler that assumes a flat memory model.

I use OpenWatcom, which was designed when flat memory model was not the standard, and it supports both 16-bit and 32-bit segmentation.
User avatar
iansjack
Member
Member
Posts: 4759
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: Trying to set up a stack segment with limits and failing

Post by iansjack »

Forget segmentation and use paging instead. You'll need it if you ever want to support 64-bit operation.
sboydlns
Posts: 12
Joined: Mon Feb 17, 2025 9:53 am

Re: Trying to set up a stack segment with limits and failing

Post by sboydlns »

First of all, thank you all for your feed back.

Let me start out by saying that I am an old mainframe programmer so I have lots of ideas about how an operating system SHOULD work that may or may not be portable to x86. Also x86 architecture is, at least at the operating system level, still new to me. This whole foray into x86 operating systems is just something to keep me occupied until spring gets here and I can get outside and do the things that really matter. So, I'm trying to keep this all as simple as possible.

I was thinking of a real memory OS with no paging. Because, to an old mainframer, 4GB of memory is unimaginably large and I can't see myself ever running out. Feel free to cast whatever stones you like here.

Having said all that, I've been thinking about the things you all said and some things come to mind.

1) It was said that since GCC uses a flat memory model it expects all segment base addresses to be zero. If that is true, then doesn't the GDT become more-or-less useless. If every task needs a flat zero thru 4GB memory space then the GDT serves no purpose. It doesn't even offer any separation of OS and user memory spaces.

2) About the segment base addresses needing to be zero. Wouldn't it be more accurate to say that all base addresses for a GCC process need to be the same, not necessarily zero. If this were true, then the OS could have a flat zero thru 4GB address space while each user process could have a flat address space of something less than 4GB starting at the address where the process was loaded. Or am I still missing something important?

3) Given the limitations of the GCC flat address constraint, it isn't difficult to see why writing a secure OS on x86 seems to be so difficult. There is no separation of code, data and stack spaces.
User avatar
Demindiro
Member
Member
Posts: 111
Joined: Fri Jun 11, 2021 6:02 am
Libera.chat IRC: demindiro
Location: Belgium
Contact:

Re: Trying to set up a stack segment with limits and failing

Post by Demindiro »

sboydlns wrote: Mon Mar 03, 2025 7:36 am First of all, thank you all for your feed back.

Let me start out by saying that I am an old mainframe programmer so I have lots of ideas about how an operating system SHOULD work that may or may not be portable to x86. Also x86 architecture is, at least at the operating system level, still new to me. This whole foray into x86 operating systems is just something to keep me occupied until spring gets here and I can get outside and do the things that really matter. So, I'm trying to keep this all as simple as possible.

I was thinking of a real memory OS with no paging. Because, to an old mainframer, 4GB of memory is unimaginably large and I can't see myself ever running out. Feel free to cast whatever stones you like here.
Whether 4GB is a lot depends on what you're doing. You certainly can fit a fully functional OS and plenty more with that space, but having more is useful when processing a lot of data. Swapping from/to disk is certainly an option, but might be slow for many random reads/writes.
Having said all that, I've been thinking about the things you all said and some things come to mind.

1) It was said that since GCC uses a flat memory model it expects all segment base addresses to be zero. If that is true, then doesn't the GDT become more-or-less useless. If every task needs a flat zero thru 4GB memory space then the GDT serves no purpose. It doesn't even offer any separation of OS and user memory spaces.

2) About the segment base addresses needing to be zero. Wouldn't it be more accurate to say that all base addresses for a GCC process need to be the same, not necessarily zero. If this were true, then the OS could have a flat zero thru 4GB address space while each user process could have a flat address space of something less than 4GB starting at the address where the process was loaded. Or am I still missing something important?
Minor caveat: hardware DMA doesn't care about segments. Though it doesn't care about paging either.
And yes, the GDT is merely an artefact, especially in 64-bit mode where it is pretty much ignored.
3) Given the limitations of the GCC flat address constraint, it isn't difficult to see why writing a secure OS on x86 seems to be so difficult. There is no separation of code, data and stack spaces.
There is: paging supports RWX bits as well as some more bits for kernel/userspace separation.

The difficulty is more so with outright hardware bugs like Meltdown and overcomplicated and/or unsuitable security models.
My OS is Norost B (website, Github, sourcehut)
My filesystem is NRFS (Github, sourcehut)
^ defunct
User avatar
iansjack
Member
Member
Posts: 4759
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: Trying to set up a stack segment with limits and failing

Post by iansjack »

Paging is not about shortage of memory. It is not the same as paging memory to disk.

Paging provides very good protection of memory areas, efficient use of physical RAM, and provides a large address space (essentially infinite when PAE is used), however much real memory you have. And it makes life easier to be able to load all user programs at the same address.

If you ever move on to 64-bit operation (and, IMO, 32-bit is pretty much dead nowadays) then you will have to use paging and you will have to use a flat address space. Why not start now?

You are correct that a GDT is a requirement that is essentially useless nowadays. That's the price of backwards compatibility.
sboydlns
Posts: 12
Joined: Mon Feb 17, 2025 9:53 am

Re: Trying to set up a stack segment with limits and failing

Post by sboydlns »

iansjack wrote: Mon Mar 03, 2025 8:12 am Paging is not about shortage of memory. It is not the same as paging memory to disk.
See, this is where my old mainframe brain is causing problems. To me, paging = swapping. Guess I'm going to have to read that section of the manual after all. Sigh!
User avatar
iansjack
Member
Member
Posts: 4759
Joined: Sat Mar 31, 2012 3:07 am
Location: Chichester, UK

Re: Trying to set up a stack segment with limits and failing

Post by iansjack »

It is confusing and many people think that paging means "paging to disk". It's actually a matter of "paging memory", i.e. dividing the logical memory address space into pages (by default 4K in size but other sizes are available) that are mapped to (usually at a different physical address) a page of physical memory. Various protections can be applied to the page, and each process can have it's own page table so it sees a different address space.

I the processor tries to access a page that is not mapped, or not present, it triggers a page fault exception which the OS can respond to. For example, in the case of a stack, you don't know in advance how big you want it to be. So you can allocate a few pages followed by a guard page, which is not present. If the stack wants to grow beyond it's allotted size the OS can allocate another page, set up a new guard page, and continue running the program. As far as the program is concerned nothing has happened. With the large address space provided by PAE you cvan set the stack at an address that is never going to conflict with any data area. And because you can run all user programs at the same (logical) address they can't access another process's memory (unless you want that to happen by sharing a page) and you can place them so that there is no possibility of them conflicting with other memory areas.

Paging is really cool. It's worth taking the time to understand it. There's quite a good article here: https://os.phil-opp.com/paging-introduction/ (it's written with Rust in mind, but still applies) that might be worth a look rather than trying to understand it just from the Intel manuals.
rdos
Member
Member
Posts: 3351
Joined: Wed Oct 01, 2008 1:55 pm

Re: Trying to set up a stack segment with limits and failing

Post by rdos »

iansjack wrote: Mon Mar 03, 2025 8:12 am Paging is not about shortage of memory. It is not the same as paging memory to disk.

Paging provides very good protection of memory areas, efficient use of physical RAM, and provides a large address space (essentially infinite when PAE is used), however much real memory you have. And it makes life easier to be able to load all user programs at the same address.
Paging and the flat memory model provide NO PROTECTION AT ALL within an application, or between device drivers in a kernel.

Also, you are mistaken if you think a 32-bit OS will either use paging or segmentation. It's perfectly possible to use both, as they solve different problems. Paging solves physical memory management issues, while segmentation solve protection issues.
iansjack wrote: Mon Mar 03, 2025 8:12 am If you ever move on to 64-bit operation (and, IMO, 32-bit is pretty much dead nowadays) then you will have to use paging and you will have to use a flat address space. Why not start now?
There is a "segmentation feature" in x86_64 (long mode) that few people seem to use, and which GCCs linker cannot handle properly. Since long mode is essentially a 64-bit capable mode that uses 32-bit offsets, you can isolate your kernel device drivers by placing them in different 4G segments, and thereby providing some rudimentary protection. However, mainstream OSes still link flat binaries where drivers are adjacent in linear memory, and therefore are essentially unprotected.
iansjack wrote: Mon Mar 03, 2025 8:12 am You are correct that a GDT is a requirement that is essentially useless nowadays. That's the price of backwards compatibility.
I make heavy use of both the GDT and LDT. They are not useless for me.
Post Reply