#GP when loading task register.

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.
Post Reply
torshie
Member
Member
Posts: 89
Joined: Sun Jan 11, 2009 7:41 pm

#GP when loading task register.

Post by torshie »

My kernel runs in 64-bit mode. If interrupts are disabled, "ltr" instruction seems to be ok, but if interrupts are enabled, "ltr" instruction cause a #GP fault. I have read related sections of the AMD manual a few times, still cannot find what goes wrong.

The getGlobal<> function template is a simple singleton pattern implementation.

This is the definition of the global descriptor table.

Code: Select all

class __attribute__((__packed__)) GlobalDescriptorTable {
public:
	enum DescriptorOffset {
		OFFSET_KERNEL_CODE = 8,
		OFFSET_KERNEL_DATA = 16,
		OFFSET_CODE32 = 24,
		OFFSET_USER_DATA = 32,
		OFFSET_USER_CODE = 40
	};

	NullDescriptor _null;
	CodeSegmentDescriptor kernelCode;
	DataSegmentDescriptor kernelData;
	NullDescriptor _code32;
	DataSegmentDescriptor userData;
	CodeSegmentDescriptor userCode;
	SystemSegmentDescriptor taskState;
	struct __attribute__((__packed__)) {
		uint16_t limit;
		void* base;
	};

	GlobalDescriptorTable();

	void load() const;
};

GlobalDescriptorTable::GlobalDescriptorTable()
		: limit(sizeof(GlobalDescriptorTable) - sizeof(limit) - sizeof(base)),
		base(this) {
	userCode.privilege = 3;
	userData._privilege = 3;

	TaskStateSegment& tss = getGlobal<TaskStateSegment>();
	taskState.setBase((uint64_t)&tss);
	taskState.setLimit(sizeof(TaskStateSegment) - 1);
	taskState.type = SystemSegmentDescriptor::AVAILABLE_TSS;
}

void GlobalDescriptorTable::load() const {
	__asm__ __volatile__(
		"lgdt %0\n"
		"mov %%ax, %%ds\n"
		"mov %%ax, %%ss\n"
		"mov %%ax, %%es\n"
		"mov %%ax, %%fs\n"
		"mov %%ax, %%gs\n"
		:
		: "m"(limit), "a"((char*)&kernelData - (char*)this)
	);
}
The definition of task state segment:

Code: Select all

struct __attribute__((__packed__)) TaskStateSegment {
	uint32_t _reserved0;
	uint64_t rsp[3];
	uint64_t ist[8]; /* ist[0] cannot bet used */
	uint64_t _reserved1;
	uint16_t _reserved2;
	uint16_t base;

	TaskStateSegment();
};

TaskStateSegment::TaskStateSegment()
		: _reserved0(0), _reserved1(0), _reserved2(0), base(0) {
	memset(rsp, 0, sizeof(rsp));
	memset(ist, 0, sizeof(ist));

	/* Check doc/memory-layout for details */
	ist[1] = KERNEL_SPACE_BASE + 0x180000;
	rsp[0] = ist[1];
}
The definition of system segment descriptor:

Code: Select all

class __attribute__((__packed__)) SystemSegmentDescriptor {
	friend class GlobalDescriptorTable;
public:
	enum SegmentDescriptorType {
		AVAILABLE_TSS = 9, BUSY_TSS = 0xB, CALL_GATE = 0xC,
		INTERRUPT_GATE = 0xE, TRAP_GATE = 0xF
	};

private:
	uint16_t limit0;
	uint32_t base0:24;
	uint8_t type:4;
	uint8_t _zero:1;
	uint8_t privilege:2;
	uint8_t present:1;
	uint8_t limit1:4;
	uint8_t available:1;
	uint8_t _ignored:2;
	uint8_t granularity:1;
	uint64_t base1:40;
	uint32_t _reserved;

	SystemSegmentDescriptor()
			: _zero(0), privilege(0), present(1), _ignored(0), granularity(0),
			_reserved(0) {}

	void setLimit(uint32_t limit) {
		limit0 = limit;
		limit1 = limit >> 16;
	}

	void setBase(uint64_t base) {
		base0 = base;
		base1 = base >> 24;
	}
};
The following code fragment shows how the system registers are loaded:

Code: Select all

	GlobalDescriptorTable& gdt = getGlobal<GlobalDescriptorTable>();
	gdt.load();
	getGlobal<InterruptDescriptorTable>().load(); <=== Load interrupt descriptor table, works as expected.
	__asm__ __volatile__ (
		"ltr %%ax\n"    <==== This line seems to be ok, probably because interrupts are disabled.
		"sti\n"    <==== This instruction is interesting, sometimes I get a double fault after this instruction.
		"ltr %%ax\n"    <==== #GP fault here, the error code is 0x30 which is the selector of TSS descriptor in GDT
		:
		: "a"((char*)(&gdt.taskState) - (char*)&gdt)
	);
However, the TSS segment does works somehow, for example, the tss.ist[1] is loaded into %rsp when the #GP fault happens. The "base" and "limit" part of TR are also loaded correctly as expected.

TR is shown as the following by QEMU:

Code: Select all

TR =0030 ffffffff80221440 00000067 00008900 DPL=0 TSS64-avl
If I just load TR once, and simply "iretq" from the mysterious double fault, everything seems to be just fine :shock:

Any idea what goes wrong?

Thanks in advance.
-torshie
torshie
Member
Member
Posts: 89
Joined: Sun Jan 11, 2009 7:41 pm

Re: #GP when loading task register.

Post by torshie »

I got it :mrgreen:
When the TR is loaded for the first time, the type of TSS descriptor will be changed to "busy TSS". When TR is loaded for the second time, a "busy TSS" would be loaded, which causes the #GP fault.

But I still cannot find out the cause of the double fault :x
Post Reply