Page 2 of 2

Re: triple-fault on jumping to kernel

Posted: Wed Aug 24, 2022 12:41 pm
by Demindiro
Schol-R-LEA wrote: It is also showing the entry point as 0xc0000008, which is definitely not what I intended. Or am I mistaken about that? Is it showing the entry point for kernel_main() rather than kstart?
Given that you have ENTRY(kernel_main) in your linker file: yes.

Re: triple-fault on jumping to kernel

Posted: Wed Aug 24, 2022 12:41 pm
by Octocontrabass
Schol-R-LEA wrote:Based on readelf, that is in fact the size of the memory footprint. This is concerning to me, I wonder if this reflects a problem in the linker script.
According to readelf, the linker has combined .text and .rodata into a single segment, so the memory footprint is correct. It's only a problem if you want .rodata to be in a segment that's read-only instead if read/execute
Schol-R-LEA wrote:It is also showing the entry point as 0xc0000008, which is definitely not what I intended. Or am I mistaken about that? Is it showing the entry point for kernel_main() rather than kstart?
I think you accidentally put the wrong symbol in your linker script.

Re: triple-fault on jumping to kernel

Posted: Wed Aug 24, 2022 1:02 pm
by Schol-R-LEA
headdesk Yes, that was a mistake. Let me correct that.

Re: triple-fault on jumping to kernel

Posted: Wed Aug 24, 2022 2:05 pm
by Demindiro
I figured it out. Very devious :D

You overwrite ds, then load from [section_offset_buffer].

There is still other stuff wrong with the loading code but at least the entry point looks right:

Code: Select all

(gdb) disas $rip,$rip+50
Dump of assembler code from 0xc0000000 to 0xc0000032:
=> 0x00000000c0000000:	call   0xc0000008
   0x00000000c0000005:	hlt    
   0x00000000c0000006:	jmp    0xc0000005
   0x00000000c0000008:	push   rbp
   0x00000000c0000009:	mov    ebp,esp
   0x00000000c000000b:	call   0xc00001c0
   0x00000000c0000010:	add    eax,0x1ff8
   0x00000000c0000015:	nop
   0x00000000c0000016:	pop    rbp
   0x00000000c0000017:	ret    
   0x00000000c0000018:	push   rbp
   0x00000000c0000019:	mov    ebp,esp
   0x00000000c000001b:	call   0xc00001c0
   0x00000000c0000020:	add    eax,0x1fe8
   0x00000000c0000025:	mov    edx,DWORD PTR [rax-0x4]
   0x00000000c000002b:	add    edx,0x2
   0x00000000c000002e:	mov    DWORD PTR [rax-0x4],edx
       
Diff:

Code: Select all

@@ -250,20 +250,22 @@ find_kernel_code_block:
         push es
         mov ax, kernel_base
         mov es, ax
-        mov dx, gs:[bx + ELF32_Program_Header.p_memsz]
-        add [section_offset_buffer], dx            ; dx = total size to allocate to the kernel code memory area
         push bx
         memset_rm 0, bx, dx
         pop bx
 
         ; move the code section of the file to the kernel code memory area
+        mov dx, [section_offset_buffer]
         push ds
         mov ax, gs
         mov ds, ax
-        memcopy_rm [section_offset_buffer], [bx + ELF32_Program_Header.p_offset], [bx + ELF32_Program_Header.p_filesz]
+        memcopy_rm dx, [bx + ELF32_Program_Header.p_offset], [bx + ELF32_Program_Header.p_filesz]
         pop ds
         pop es
 
+        mov dx, gs:[bx + ELF32_Program_Header.p_memsz]
+        add [section_offset_buffer], dx            ; dx = total size to allocate to the kernel code memory area^M
+
     .loop_continue:
         ; advance the pointer through the header array
         add bx, ELF32_Program_Header_size

Re: triple-fault on jumping to kernel

Posted: Wed Aug 24, 2022 3:08 pm
by Schol-R-LEA
OK, that did in fact fix the immediate problem of how the code section is getting written to. The current version of the loading code (which I will push to the repo ASAP) is

Code: Select all

load_kernel_code:
        push ax
        push es
        mov si, kernel_filename
        mov di, word [bp - stg2_parameters.directory_buffer]
        mov cx, Root_Entries
        mov bx, dir_entry_size
        call near seek_directory_entry
        cmp di, word 0
        jnz .read_directory

        write no_kernel
        jmp local_halt_loop

    .read_directory:
        call read_directory_details
        write kernel_file_found

        ; reset the disk drive

        call near reset_disk

        mov di, word [bp - stg2_parameters.fat_0]
        mov ax, kernel_raw_base
        mov es, ax
        mov si, kernel_raw_offset
        call near fat_to_file
        pop es
        pop ax
        write kernel_loaded


find_kernel_code_block:
        push gs
        mov ax, kernel_raw_base
        mov gs, ax
        mov al, byte gs:[kernel_raw_offset + ELF32_Header.magic]
        cmp al, byte ELF_Magic
        je .test_signature
        write invalid_elf_magic
        jmp local_halt_loop

    .test_signature:
        mov cx, 3
        mov di, kernel_raw_offset + ELF32_Header.sig
        push es
        mov ax, kernel_raw_base
        mov es, ax
        mov si, ELF_Sig
    repe cmpsb
        pop es
        je .test_elf_endianness
        write invalid_elf_sig
        jmp local_halt_loop

    .test_elf_endianness:
        mov al, byte gs:[kernel_raw_offset + ELF32_Header.endianness]
        cmp al, ELF_little_endian
        je .test_elf_isa
        write elf_big_endian
        jmp local_halt_loop

    .test_elf_isa:
        mov al, byte gs:[kernel_raw_offset + ELF32_Header.isa]
        cmp al, ELF_ISA_x86
        je .test_elf_executable
        write elf_not_x86
        jmp local_halt_loop

    .test_elf_executable:
        mov ax, word gs:[kernel_raw_offset + ELF32_Header.type]
        cmp ax, ELF_type_executable
        je .read_elf_header_table
        write non_executable_elf_file
        jmp local_halt_loop

    .read_elf_header_table:
        write valid_elf_file
        ; set up an offset for the code sections in memory
        mov [section_offset], word kcode_offset
        mov cx, gs:[kernel_raw_offset + ELF32_Header.program_table_entry_count]

        write number_of_sections
        mov ax, cx
        call print_decimal_word
        write newline

        ; while most of the size fields in the ELF32 header are 32-bit dwords,
        ; a number of places in this code assume that the amounts in question
        ; are under 64KiB, and can be read with a 16-bit word. This is a reasonable
        ; assumption with the current code, but may not hold if the kernel grows
        ; beyond it's current <64KiB size.

        ; bx = pointer to the first program header - location must be under 64KiB to work
        mov bx, word gs:[kernel_raw_offset + ELF32_Header.program_header_table]

    .program_header_loop:
        ; check to see if the section is loadable
        mov ax, gs:[bx + ELF32_Program_Header.p_type]
        cmp ax, ELF_Header_loadable_type
        jne .loop_continue                         ; not a loadable section, skip

        ; found a loadable section
        ; first, clear the region of memory to load to
        push es
        mov ax, kernel_base
        mov es, ax                                 ; set es to the segment to later map to higher half
        mov dx, gs:[bx + ELF32_Program_Header.p_memsz]
        memset_rm 0, dx, [section_offset]

        ; move the code section of the file to the kernel code memory area
        ; keep ES set to the destination segment
        mov dx, [section_offset]
        push ds                                    ; temporarily set DS = GS so the macro works on the right segments
        mov ax, gs
        mov ds, ax

        memcopy_rm dx, [bx + ELF32_Program_Header.p_offset], [bx + ELF32_Program_Header.p_filesz]
        pop ds
        pop es

        mov dx, gs:[bx + ELF32_Program_Header.p_memsz]
        add [section_offset], dx                   ; dx = total size to allocate to the kernel code memory area

    .loop_continue:
        ; advance the pointer through the header array
        add bx, ELF32_Program_Header_size
        add [section_offset], dx            ; dx = total size to allocate to the kernel code memory area
        loop .program_header_loop
        pop gs
The disassembly is now:

Code: Select all

   0xc0000000:	call   0xc0000008
   0xc0000005:	hlt    
   0xc0000006:	jmp    0xc0000005
   0xc0000008:	push   %ebp
   0xc0000009:	mov    %esp,%ebp
   0xc000000b:	sub    $0x8,%esp
   0xc000000e:	call   0xc0000158
   0xc0000013:	sub    $0x8,%esp
   0xc0000016:	push   $0x7
   0xc0000018:	push   $0xc0001000
   0xc000001d:	call   0xc0000110
   0xc0000022:	add    $0x10,%esp
   0xc0000025:	nop
   0xc0000026:	leave  
   0xc0000027:	ret    
   0xc0000028:	push   %ebp
   0xc0000029:	mov    %esp,%ebp
   0xc000002b:	mov    0xc0002004,%eax
   0xc0000030:	add    $0x2,%eax
   0xc0000033:	mov    %eax,0xc0002004
   0xc0000038:	movzwl 0xc0003002,%eax
   0xc000003f:	mov    $0x50,%edx
   0xc0000044:	cmp    %dx,%ax
   0xc0000047:	jae    0xc000005b
   0xc0000049:	movzwl 0xc0003002,%eax
   0xc0000050:	add    $0x1,%eax
   0xc0000053:	mov    %ax,0xc0003002
   0xc0000059:	jmp    0xc0000085
   0xc000005b:	movw   $0x0,0xc0003002
   0xc0000064:	movzwl 0xc0003000,%eax
   0xc000006b:	mov    $0x19,%edx
   0xc0000070:	cmp    %dx,%ax
   0xc0000073:	jae    0xc0000085
   0xc0000075:	movzwl 0xc0003000,%eax
   0xc000007c:	add    $0x1,%eax
   0xc000007f:	mov    %ax,0xc0003000
   0xc0000085:	nop
   0xc0000086:	pop    %ebp
   0xc0000087:	ret    
   0xc0000088:	push   %ebp
   0xc0000089:	mov    %esp,%ebp
   0xc000008b:	sub    $0x18,%esp
   0xc000008e:	mov    0x8(%ebp),%edx
   0xc0000091:	mov    0xc(%ebp),%eax
   0xc0000094:	mov    %dx,-0x14(%ebp)
   0xc0000098:	mov    %ax,-0x18(%ebp)
   0xc000009c:	mov    $0x19,%eax
   0xc00000a1:	movzwl %ax,%edx
   0xc00000a4:	mov    $0x50,%eax
   0xc00000a9:	movzwl %ax,%eax
   0xc00000ac:	imul   %edx,%eax
   0xc00000af:	add    $0xb8000,%eax
   0xc00000b4:	mov    %eax,-0x4(%ebp)
   0xc00000b7:	mov    0xc0002004,%eax
   0xc00000bc:	mov    %eax,%edx
   0xc00000be:	mov    -0x4(%ebp),%eax
   0xc00000c1:	add    %edx,%eax
   0xc00000c3:	mov    %eax,0xc0002004
   0xc00000c8:	movzwl -0x14(%ebp),%eax
   0xc00000cc:	mov    %ax,0xc0003002
   0xc00000d2:	movzwl -0x18(%ebp),%eax
   0xc00000d6:	mov    %ax,0xc0003000
   0xc00000dc:	nop
   0xc00000dd:	leave  
   0xc00000de:	ret    
   0xc00000df:	push   %ebp
   0xc00000e0:	mov    %esp,%ebp
   0xc00000e2:	sub    $0x8,%esp
   0xc00000e5:	mov    0x8(%ebp),%edx
   0xc00000e8:	mov    0xc(%ebp),%eax
   0xc00000eb:	mov    %dl,-0x4(%ebp)
   0xc00000ee:	mov    %al,-0x8(%ebp)
   0xc00000f1:	mov    0xc0002004,%eax
   0xc00000f6:	movzbl -0x4(%ebp),%edx
   0xc00000fa:	mov    %dl,(%eax)
   0xc00000fc:	mov    0xc0002004,%eax
   0xc0000101:	movzbl -0x8(%ebp),%edx
   0xc0000105:	mov    %dl,0x1(%eax)
   0xc0000108:	call   0xc0000028
   0xc000010d:	nop
   0xc000010e:	leave  
   0xc000010f:	ret    
   0xc0000110:	push   %ebp
   0xc0000111:	mov    %esp,%ebp
   0xc0000113:	sub    $0x14,%esp
   0xc0000116:	mov    0xc(%ebp),%eax
   0xc0000119:	mov    %al,-0x14(%ebp)
   0xc000011c:	movl   $0x0,-0x4(%ebp)
   0xc0000123:	jmp    0xc0000145
   0xc0000125:	movzbl -0x14(%ebp),%edx
   0xc0000129:	mov    -0x4(%ebp),%ecx
   0xc000012c:	mov    0x8(%ebp),%eax
   0xc000012f:	add    %ecx,%eax
   0xc0000131:	movzbl (%eax),%eax
   0xc0000134:	movsbl %al,%eax
   0xc0000137:	push   %edx
   0xc0000138:	push   %eax
   0xc0000139:	call   0xc00000df
   0xc000013e:	add    $0x8,%esp
   0xc0000141:	addl   $0x1,-0x4(%ebp)
   0xc0000145:	mov    -0x4(%ebp),%edx
   0xc0000148:	mov    0x8(%ebp),%eax
   0xc000014b:	add    %edx,%eax
   0xc000014d:	movzbl (%eax),%eax
   0xc0000150:	test   %al,%al
   0xc0000152:	jne    0xc0000125
   0xc0000154:	nop
   0xc0000155:	nop
   0xc0000156:	leave  
   0xc0000157:	ret    
   0xc0000158:	push   %ebp
   0xc0000159:	mov    %esp,%ebp
   0xc000015b:	sub    $0x10,%esp
   0xc000015e:	movl   $0x0,-0x4(%ebp)
   0xc0000165:	jmp    0xc0000177
   0xc0000167:	push   $0x0
   0xc0000169:	push   $0x20
   0xc000016b:	call   0xc00000df
   0xc0000170:	add    $0x8,%esp
   0xc0000173:	addl   $0x1,-0x4(%ebp)
   0xc0000177:	mov    $0x7d0,%eax
   0xc000017c:	movzwl %ax,%eax
   0xc000017f:	cmp    %eax,-0x4(%ebp)
   0xc0000182:	jl     0xc0000167
   0xc0000184:	nop
   0xc0000185:	nop
   0xc0000186:	leave  
   0xc0000187:	ret    
It looks as if the code should now run, and it appears that it does when I step into it - except that I now need to debug the terminal-handling code. :-(

Re: triple-fault on jumping to kernel

Posted: Wed Aug 24, 2022 3:14 pm
by Schol-R-LEA
Octocontrabass wrote:
Schol-R-LEA wrote:Based on readelf, that is in fact the size of the memory footprint. This is concerning to me, I wonder if this reflects a problem in the linker script.
According to readelf, the linker has combined .text and .rodata into a single segment, so the memory footprint is correct. It's only a problem if you want .rodata to be in a segment that's read-only instead if read/execute
OK, while that was not a critical issue in the moment as I was trying to get the loader's basic functionality working, it is definitely something I will want to address now. I assume that the solution will be to set up a separate set of pages for the .rodata mapped to someplace other than where it is getting loaded right now, correct? That part is straightforward, I would think; it is the changes to the linker script and the ELF loader itself that I am not clear about.

Re: triple-fault on jumping to kernel

Posted: Wed Aug 24, 2022 4:34 pm
by Octocontrabass
Schol-R-LEA wrote:OK, while that was not a critical issue in the moment as I was trying to get the loader's basic functionality working, it is definitely something I will want to address now. I assume that the solution will be to set up a separate set of pages for the .rodata mapped to someplace other than where it is getting loaded right now, correct?
No need, the current location is fine.
Schol-R-LEA wrote:That part is straightforward, I would think; it is the changes to the linker script and the ELF loader itself that I am not clear about.
For removing the write permission, just clear the "writable" bit in the PTEs for the pages that aren't supposed to be writable, and set CR0.WP. (If you do this to pages that are already marked present while paging is enabled, you also need to flush those pages from the TLB.) No changes to your linker script are necessary for this.

For removing the execute permission, it's not so simple. The PTEs don't have a "not executable" bit unless you're using PAE, so the only way to limit which addresses are executable is the code segment limit. Since your kernel is in the higher half, everything in userspace will remain executable, and fast system call instructions don't support a non-flat code segment, so it may not be worth the effort. You also need to change how you link the kernel... but I have no idea what needs to be changed because I've never gotten this far. (Maybe add "-z separate-code" to your linker flags? Or "-Wl,-z,separate-code" if you link with GCC.)

Re: triple-fault on jumping to kernel

Posted: Wed Aug 24, 2022 9:13 pm
by nullplan
Schol-R-LEA wrote:
Octocontrabass wrote:According to readelf, the linker has combined .text and .rodata into a single segment, so the memory footprint is correct. It's only a problem if you want .rodata to be in a segment that's read-only instead if read/execute
OK, while that was not a critical issue in the moment as I was trying to get the loader's basic functionality working, it is definitely something I will want to address now. I assume that the solution will be to set up a separate set of pages for the .rodata mapped to someplace other than where it is getting loaded right now, correct? That part is straightforward, I would think; it is the changes to the linker script and the ELF loader itself that I am not clear about.
Honestly, I wouldn't bother. .rodata and .text can live very well together, united by their lack of a writable flag. As long as they are mapped read-only (you do not use self-modifying code, right?), you will reap most of the benefits.

But if you must positively have .rodata mapped without execute permissions (which is difficult without PAE, as Octo already said), then you can put a page alignment into your linker script between the .text and .rodata sections.

Code: Select all

. = ALIGN(4096);
If that doesn't make the linker generate a new segment on its own, you can also force it to with the PHDRS statement.
Octocontrabass wrote:For removing the execute permission, it's not so simple. The PTEs don't have a "not executable" bit unless you're using PAE, so the only way to limit which addresses are executable is the code segment limit. Since your kernel is in the higher half, everything in userspace will remain executable, and fast system call instructions don't support a non-flat code segment, so it may not be worth the effort.
You have different CS selectors for kernel and user space. So you can have a kernel execute limit and a user execute limit. That won't help you with the kernel jumping into userspace, but it does help with the execution of read-only data. Biggest problem is going to be the granularity. The segment limit is a twenty-bit number, and with the base being zero, it should encode the highest executable address. For higher-half addresses it can only do so at page granularity. So the break between .text and .rodata needs to be page aligned.
Octocontrabass wrote:You also need to change how you link the kernel... but I have no idea what needs to be changed because I've never gotten this far. (Maybe add "-z separate-code" to your linker flags? Or "-Wl,-z,separate-code" if you link with GCC.)
You can simply use a linker script. Linker scripts are recommended to kernels anyway, because you can set a lot of details in them, add symbols around certain sections of your data (custom data sections really help with collecting a bunch of different things from various object files). And in a linker script, you can add the above mentioned alignment, and even force the use of another program header.

Re: triple-fault on jumping to kernel

Posted: Wed Aug 24, 2022 11:31 pm
by Schol-R-LEA
nullplan wrote:
Schol-R-LEA wrote:
Octocontrabass wrote:According to readelf, the linker has combined .text and .rodata into a single segment, so the memory footprint is correct. It's only a problem if you want .rodata to be in a segment that's read-only instead if read/execute
OK, while that was not a critical issue in the moment as I was trying to get the loader's basic functionality working, it is definitely something I will want to address now. I assume that the solution will be to set up a separate set of pages for the .rodata mapped to someplace other than where it is getting loaded right now, correct? That part is straightforward, I would think; it is the changes to the linker script and the ELF loader itself that I am not clear about.
Honestly, I wouldn't bother. .rodata and .text can live very well together, united by their lack of a writable flag. As long as they are mapped read-only (you do not use self-modifying code, right?), you will reap most of the benefits.
Fair enough, though in the current version I haven't actually marked the pages as r/o yet (oops). Though since the actual flag (bit 2 in a Page Table Entry) is r/w on set, I guess that the default is correct.

As for self-modifying code, that will have to wait until I can write a suitable means of defining quajects, and that's not part of my plans for this particular OS project. :-)

Note that I was able to get the (very simple and limited) console-handling code working enough to clear the screen and write a string (though I haven't handled scrolling or newlines yet), and have pushed that to the repo.

Re: triple-fault on jumping to kernel

Posted: Thu Aug 25, 2022 12:42 am
by xeyes
nullplan wrote:You have different CS selectors for kernel and user space. So you can have a kernel execute limit and a user execute limit. That won't help you with the kernel jumping into userspace
SMEP can help with that, but kernel jumping into userspace would be a rare bug. Out of bound access trashing large chunks of code seems way more likely.

Re: triple-fault on jumping to kernel

Posted: Thu Aug 25, 2022 1:51 pm
by Schol-R-LEA
I'm not entirely sure where to go from here; I would say I'll probably need to address the IDT and ISRs next, but I am not sure if there are any other things I need to address first.

EDIT: I just thought of something else I need to do: retrieve the BIOS and disk directory data that I put into the upper portion of the HMA while in real mode, which is now mapped to somewhere like 0xC000BF6A. Assuming that the code for storing it there works correctly in the first place.

Re: triple-fault on jumping to kernel

Posted: Thu Aug 25, 2022 4:14 pm
by Octocontrabass
Schol-R-LEA wrote:I'm not entirely sure where to go from here; I would say I'll probably need to address the IDT and ISRs next, but I am not sure if there are any other things I need to address first.
Personally, I would want some basic memory management to dynamically allocate the IDT. That might go beyond your goals of a simple kernel, though. (And it might be harder to debug since you can't set up exception handlers without an IDT.)

I would also want to set up a new GDT, just to get away from depending on the bootloader's GDT. That might be important later if you decide your kernel needs to use different selectors for its code and data segments.

Re: triple-fault on jumping to kernel

Posted: Thu Aug 25, 2022 8:48 pm
by Schol-R-LEA
Octocontrabass wrote:
Schol-R-LEA wrote:I'm not entirely sure where to go from here; I would say I'll probably need to address the IDT and ISRs next, but I am not sure if there are any other things I need to address first.
Personally, I would want some basic memory management to dynamically allocate the IDT. That might go beyond your goals of a simple kernel, though. (And it might be harder to debug since you can't set up exception handlers without an IDT.)
Oddly enough, memory management - or more specifically, recovering the memory map - is in fact what I've been working on, though I've run into a serious problem with that, in that it isn't getting saved to the memory locations I intended to put the memory map into. I really can't do memory management until I know what is valid system memory. Right now, I just need to figure out how to successfully pass that and some other data structures to the kernel.
Octocontrabass wrote:I would also want to set up a new GDT, just to get away from depending on the bootloader's GDT. That might be important later if you decide your kernel needs to use different selectors for its code and data segments.
I do intend to do that, yes, but it isn't a priority at the moment.