OSDev.org https://forum.osdev.org/ |
|
Trying to make my very first GDT https://forum.osdev.org/viewtopic.php?f=1&t=32561 |
Page 1 of 1 |
Author: | 4dr14n31t0r [ Sun Nov 12, 2017 4:07 pm ] |
Post subject: | Trying to make my very first GDT |
I am trying to enter the protected mode without labels because I want to be sure that I understand how everything works. This is my code, that should print the characters from 'A' to 'F': Code: 1 %macro set_magic_number 0.nolist In the lines from 14 to 19 I set up the GDT and start the protected mode.2 times 510 - ( $ - $$ ) db 0 3 db 0x55, 0xAA 4 %endmacro 5 6 %macro print_c 1.nolist 7 mov ah, 0x0E 8 mov al, %1 9 int 0x10 10 %endmacro 11 12 00000000 FA cli 13 14 00000001 B8C007 mov ax, 0x07C0 15 00000004 8EC0 mov es, ax 16 00000006 260F01167D00 lgdt [es:0x007D] 17 0000000C 0F20C0 mov eax, cr0 18 0000000F 0C01 or al, 1 19 00000011 0F22C0 mov cr0, eax 20 00000014 FB sti 21 00000015 EA00000100 jmp 0x0001:0x0000 22 23 0000001A B40EB030CD10 print_c '0' 24 00000020 B40EB031CD10 print_c '1' 25 00000026 B40EB032CD10 print_c '2' 26 0000002C B40EB033CD10 print_c '3' 27 00000032 B40EB034CD10 print_c '4' 28 00000038 B40EB035CD10 print_c '5' 29 0000003E B40EB036CD10 print_c '6' 30 00000044 B40EB037CD10 print_c '7' 31 0000004A B40EB038CD10 print_c '8' 32 00000050 B40EB039CD10 print_c '9' 33 00000056 B40EB041CD10 print_c 'A' 34 0000005C B40EB042CD10 print_c 'B' 35 00000062 B40EB043CD10 print_c 'C' 36 00000068 B40EB044CD10 print_c 'D' 37 0000006E B40EB045CD10 print_c 'E' 38 00000074 B40EB046CD10 print_c 'F' 39 40 0000007A FB sti 41 42 0000007B EBFE stop:jmp stop 43 44 0000007D 0010 db 0x00,0x10 45 0000007F 00007C7B db 0x00,0x00,0x7C,0x83 46 47 00000083 0000000000000000 dq 0x0 48 49 0000008B FFFF dw 0xFF_FF 50 0000008D 007C56 db 0x00,0x7C,0x56 51 00000090 4F db 0b01011111 52 00000091 F0 db 0b1111_0000 53 00000092 00 db 0x00 54 55 00000093 00<rept>55AA set_magic_number In the line number 21 I make a far jump that should make the program print the caracters from 'A' to 'F'. In the line 44 I put the size of the GDT. Each entry has a size of 8 bytes and I only have 2 entries: The null descriptor (because everybody recommends to put it) and the descriptor that I want to test. Total: 16 bytes (0x10) In the line 45 I put the start location of the GDT: 0x7C00 (where the bootloader is loaded) + 0x0083 = 0x7C83 In the line 47 I set the null descriptor In the line 49 I set the maximum size possible for a segment, as also did in line 52 with the four ones In the line 50 I put the location where I would like to set the code segment: the location of the print_c 'A' instruction. In the line 51 I set the access byte with the following values: - bit 0: This bit tells us whether the segment is accessed by CPU, so it should be set to zero - bit 1: Make the code segment read-only instead of unreadable - bit 2: Not conforming. This way we don't need any privileges - bit 3: Is a code segment, so it should be executable. - bit 4: Reserved, always 1 - bit 5 and 6: 11 (3 in binary) because is the lowest privilege level.This way the segment is more accessible. Anyway, bit 2 make this bit useless. - bit 7: Wheter the segment is present or not. I don't get it, but I set it to 1 because I think it should be 1 to be usable. The first 4 bits in the line 52 are the size again, so I set it to 1111 because I want it to be the greatest possible so I don't have problems with the size (I'm just testing the GDT) The last 4 bits in the line 52 I set the entry flag bits this way: - bit 0 and 1: Reserved, always zero - bit 2: size --> zero (16 bit mode) - bit 3: granularity --> I don't want this, so zero. And finally the line 53 with the last byte of the address of the segment. However, this doesn't do anything. What I am missing? |
Author: | mikegonta [ Sun Nov 12, 2017 5:00 pm ] |
Post subject: | Re: Trying to make my very first GDT |
4dr14n31t0r wrote: However, this doesn't do anything. What I am missing? A simple way to access the real mode BIOS while in protected mode.
|
Author: | Octocontrabass [ Sun Nov 12, 2017 5:41 pm ] |
Post subject: | Re: Trying to make my very first GDT |
4dr14n31t0r wrote: What I am missing? Quite a few things. You need an IDT before you can enable interrupts in protected mode. Segment selector 0x0001 is the null segment. Other than a handful of exceptions, you can't use the BIOS in protected mode. Every group of bytes you've written is backwards (x86 is little endian). Every group of bits you've written is backwards (x86 is insane but not that insane). I suggest not wasting your time with 16-bit segments in protected mode. They're completely obsolete and useless on any CPU newer than the 286. I also suggest not wasting your time with nonzero segments in real mode and nonzero segment base addresses in protected mode. mikegonta wrote: A simple way to access the real mode BIOS while in protected mode. That doesn't exist, so there's no point in wasting time trying to find it. (Anyone who claims otherwise doesn't understand it well enough.) |
Author: | iansjack [ Mon Nov 13, 2017 12:52 am ] |
Post subject: | Re: Trying to make my very first GDT |
Code: jmp 0x0001:0x0000 cannot be valid in protected mode. It is using the first (null) descriptor, which is invalid.
|
Author: | Schol-R-LEA [ Tue Nov 14, 2017 11:13 am ] |
Post subject: | Re: Trying to make my very first GDT |
Octocontrabass wrote: 4dr14n31t0r wrote: What I am missing? mikegonta wrote: A simple way to access the real mode BIOS while in protected mode. That doesn't exist, so there's no point in wasting time trying to find it. (Anyone who claims otherwise doesn't understand it well enough.) I'm pretty sure Mike was being sarcastic, but this reply is still apropos because a novice wouldn't have the context to understand why it is a sarcastic answer. It's a general problem with text-only communication channels, and is, among other things, a prime reason why emoticons (and now, emoji) exist. |
Author: | 4dr14n31t0r [ Tue Nov 14, 2017 6:03 pm ] |
Post subject: | Re: Trying to make my very first GDT |
I am working very hard to get this protected mode working, but I'm still stuck. This is how my code looks like now: Code: 1 VGA_COLOR_LIGHT_RED equ 0xC 2 VGA_COLOR_LIGHT_GREEN equ 0xA 3 4 ;pos bg fg ch 5 %macro write_c 4 6 mov bx, 0x10 7 mov es, bx 8 mov bx, %1 9 shl bx, 0x1 10 mov ah, %2 11 shl ah, 0x4 12 or ah, %3 13 mov al, %4 14 mov [es:bx], ax 15 %endmacro 16 17 %macro set_magic_number 0 18 times 510 - ( $ - $$ ) db 0 19 db 0x55, 0xAA 20 %endmacro 21 22 org 0x7C00 23 24 00000000 FA cli 25 26 00000001 0F0116[2C00] lgdt [gdt_ptr] 27 28 00000006 0F20C0 mov eax, cr0 29 00000009 0C01 or al, 1 30 0000000B 0F22C0 mov cr0, eax 31 32 0000000E EA[1300]0800 jmp 0x8:next 33 next: 34 35 write_c 0, VGA_COLOR_LIGHT_RED, VGA_COLOR_LIGHT_GREEN, 'X' 36 00000013 BB1000 <1> mov bx, 0x10 37 00000016 8EC3 <1> mov es, bx 38 00000018 BB0000 <1> mov bx, %1 39 0000001B D1E3 <1> shl bx, 0x1 40 0000001D B40C <1> mov ah, %2 41 0000001F C0E404 <1> shl ah, 0x4 42 00000022 80CC0A <1> or ah, %3 43 00000025 B058 <1> mov al, %4 44 00000027 268907 <1> mov [es:bx], ax 45 46 0000002A EBFE stop: jmp stop 47 48 49 50 51 gdt_ptr: 52 0000002C 0017 db 0,8*3-1 53 0000002E 00007C32 db 0,0,0x7C,0x32 54 55 ; null descriptor 56 00000032 0000000000000000 dq 0 57 58 ; code descriptor 59 ; #0-------------- #1-------------- #2------ #3#4#5#6#7#8 #9#A-- #B#C#D#E#F------ 60 ; 0b1111111111111111_0000000000000000_00000000_0_1_0_1_1_00_1_1111_0_0_1_0_00000000 61 0000003A FFFF00000059F200 db 0xff,0xff, 0x00,0x00, 0x00, 0x59, 0xf2, 0x00 62 63 ; data descriptor: 64 ; #0-------------- #1-------------- #2------ #3#4#5#6#7#8 #9#A-- #B#C#D#E#F------ 65 ; 0b1111111111111111_0000101110000000_00000000_0_1_0_0_1_00_1_1111_0_0_1_0_00000000 66 00000042 FFFF0B800049F200 db 0xff,0xff, 0x0b,0x80, 0x00, 0x49, 0xf2, 0x00 67 68 set_magic_number 69 0000004A 00<rept> <1> times 510 - ( $ - $$ ) db 0 70 000001FE 55AA <1> db 0x55, 0xAA I am not using interrupts so I don't need an IDT because I am writting to the screen though video memory (0x8B000). In the line number 32 I make a far jump to 0x8:next to set the cs register with the second descriptor (Each descriptor's size is 8 bytes and the second is the code descriptor, so I think I should use 0x8). In the line 16 I load 'es' with the value 0x10 because the data descriptor is the second descriptor and 0x8*2 = 0x10 This time I use db with commas instead of dw, dd or dq because I don't want to have problems with little endian. These are now the values of the descriptors: #0 & #A: Limit with the maximum possible value. This is a test program that is only supposed to print something in screen. I don't want problems with the size. #1, #2 & #F: Base at the start of the memory. #3: If the segment has been accessed or not. Therefore, zero #4: Read/Write. I don't want to have problems so I set this to 1 #5: Direction/Conforming. Zero means no privilege level needed to use. #6: Executable. This is only in the first case. #7: Reserved, always 1 #8: Kernel privilege. #9: Present. I want to use it. Therefore 1. #B & #C: Reserved, always zero. #D: Size. As Octocontrabass suggest I use 32 bit. #E: Granularity. I think I don't need it. I am spending a lot of time trying to find a way to get this working and I really would appreciate a minimal example that only have GDT and print something using it. This tutorial http://wiki.osdev.org/GDT_Tutorial use C code, but I want to do this in NASM. |
Author: | MichaelPetch [ Tue Nov 14, 2017 8:03 pm ] |
Post subject: | Re: Trying to make my very first GDT |
To sum up the errors: x86 is little Endian. Some of your structures and data have bytes in reverse order. You shouldn't have to hard code addresses like 0x7c32 into data structures. You can use labels for that. In your GDT you have the bytes in the correct order but the access byte and flags have all the bits in reverse order. You are also missing a bits 32 directive after the JMP so you end up with incorrect instruction encoding. Should look like: Code: jmp 0x8:next You should be setting ES, DS, SS descriptors to 0x0010 and the base in the descriptor should be 0x00000000 not 0x000b8000 (I know what you are trying to do but it is very inefficient that way). The normal way makes a 4gb flat model and most x86 CPUs are optimized for the case where the base is 0x00000000. If you want to write to video memory just move with something like mov [b8000], 0x####. You can modify your write_c macro to compute the address and the attr/character at assembly time. bits 32 next: A version of your program that would likely work: Code: VGA_COLOR_LIGHT_RED equ 0xC In the code above the macro has something an LCP (Length Changing Prefix) stall because it uses an imm16 value in a move to a memory operand. It is cheaper (performance wise) to do the mov of an imm32 value into a 32-bit register and then move the lower 16-bit register into memory. The change would look like: VGA_COLOR_LIGHT_GREEN equ 0xA ;pos bg fg ch %macro write_c 4 mov word [0xb8000+(%1*2)],(%2<<12) | (%3<<8) | %4 %endmacro %macro set_magic_number 0 times 510 - ( $ - $$ ) db 0 db 0x55, 0xAA %endmacro org 0x7C00 cli lgdt [gdt_ptr] mov eax, cr0 or al, 1 mov cr0, eax jmp 0x8:next bits 32 next: mov eax, 0x10 mov es, ax ; ES=DS=SS=0x10 mov ds, ax mov ss, ax write_c 0, VGA_COLOR_LIGHT_RED, VGA_COLOR_LIGHT_GREEN, 'X' write_c 1, VGA_COLOR_LIGHT_RED, VGA_COLOR_LIGHT_GREEN, 'Y' stop: jmp stop gdt_ptr: ;db 0,8*3-1 ;db 0,0,0x7C,0x32 dw endgdt-startgdt-1 dd startgdt align 4 startgdt: ; null descriptor dq 0 ; code descriptor ; #01------------- #02------------- #03----- #04 #05 #06 #07 #08 #09 #10 #11- #12 #13 #14 #15 #16----- ; 0b1111111111111111___0000000000000000___00000000___0___1___0___1___1___00___1___1111___0___0___1___0___00000000 db 0xff,0xff db 0x00,0x00 db 0x00 db 0x9A db 0xCF db 0x00 ; data descriptor: ; #01------------- #02------------- #03----- #04 #05 #06 #07 #08 #09 #10 #11- #12 #13 #14 #15 #16----- ; 0b1111111111111111___0000101110000000___00000000___0___1___0___0___1___00___1___1111___0___0___1___0___00000000 db 0xff,0xff ; db 0x0b,0x80 db 0x00,0x00 db 0x00 db 0x92 db 0xCF db 0x00 endgdt: set_magic_number Code: ;pos bg fg ch On an unrelated note, you should consider making sure the A20 line is enabled. If you run it on hardware or a virtual machine where this isn't the case then memory addresses starting on an odd numbered megabyte boundary will not work as expected.
%macro write_c 4 mov eax, (%2<<12) | (%3<<8) | %4 mov word [0xb8000+(%1*2)], ax %endmacro |
Author: | 4dr14n31t0r [ Thu Nov 16, 2017 2:35 pm ] |
Post subject: | Re: Trying to make my very first GDT |
MichaelPetch wrote: To sum up the errors: x86 is little Endian Ok, I get it. But I still don't get something:This wiki(http://wiki.osdev.org/Global_Descriptor_Table) says that bit 0 of access byte is the accessed bit and bit 7 is the present bit. 0 is smaller than 7, so it goes before. But you say that the bits are in reverse order. WTF? Here is the image: And I have a theory that I would like to confirm: In the following image the bits from 0 to 16 are the limit. Is that also in little endian? For example, if I want the limit to be 0xFFF0 (65520 in decimal), and the start of the descriptor is, let say, at 0x0800 (for example), How would the memory at that location be filled? a) 0x0800 --> 0xFF; 0x0801 --> 0xF0 b) 0x0800 --> 0xF0; 0x0801 --> 0xFF The image clearly shows that the limit is the bits from 0 to 15 in memory, but doesn't specify if those 2 bytes should be in little endian or not. I know that the x86 works with little endian, but I don't know how computer internally accesses that memory. I think that this could sound kinda stupid, but if I access the limit byte by byte then the limit would be in big endian. I think so because I see a separate byte of limit in bits 48-51 and I don't know if the computer first take the word from bits 0-15 and the byte from 48-51 and then do some operations to get the Limit bytes in the correct order or just obtain the entire limit byte by byte to easily obtain it in the correct order. I think I am overthinking this simple concept. Something similar happens to me with the jmp instruction: I know that I shouldn't be hardcoding the location of the jmp instruction, but when I do something like: Code: jmp ABCD I have to see how NASM internally assembles that instruction because I don't know if NASM think that ABCD is the location where I want to jump without little endian conversion and should make that conversion for me, or if I already considered the little endian and only have to put that number in the output binary file as is.
|
Author: | iansjack [ Thu Nov 16, 2017 2:54 pm ] |
Post subject: | Re: Trying to make my very first GDT |
I think that you perhaps need to brush up on your understanding of the processor and assembler coding before proceeding further. Try assembling some simple programs, and run them under a debugger such as gdb. Examine the memory locations and registers, and see how they relate to the instructions and change as the instructions are executed. Without a thorough understanding of the underlying hardware, and how it works in practice, you are just going to flounder with the more complicated stuff. |
Author: | Octocontrabass [ Sun Nov 19, 2017 10:46 am ] |
Post subject: | Re: Trying to make my very first GDT |
4dr14n31t0r wrote: This wiki(http://wiki.osdev.org/Global_Descriptor_Table) says that bit 0 of access byte is the accessed bit and bit 7 is the present bit. 0 is smaller than 7, so it goes before. But you say that the bits are in reverse order. WTF? There's bit order, and then there's byte order. Bits are always written in the same order. The most-significant bit is on the left and the least-significant bit is on the right. This matches how numbers are written in base 10, so to keep things simple we write numbers the same way in every other base system too. Bits are not always numbered the same way. Intel has decided that the least-significant bit is bit 0, and any bits of greater significance are numbered from there. So, for example, a constant with only bit 7 set would be written 0b10000000 or 0x80, and a constant with only bit 15 set would be written 0b10000000_00000000 or 0x8000. Bytes are not always written in the same order. Intel says the least-significant byte goes at the lowest memory address, which means in your code you have to put the byte containing bit 0 first. Your assembler will do this automatically when you write a value that takes more than one byte; for example, "dw 0x1234" is equivalent to "db 0x34,0x12". Unless otherwise specified, you should assume for everything x86 that bit 0 is the least-significant bit and the least-significant byte is at the lowest address. |
Page 1 of 1 | All times are UTC - 6 hours |
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group http://www.phpbb.com/ |