Page 1 of 1

Video RAM starts on index 1 instead of index 0

Posted: Sun Sep 18, 2011 7:16 am
by frost
I've implemented the bare bones tutorial but I think that something is a bit wrong. I have the following code

Code: Select all

void main(void)
{
    unsigned char *videoram = (unsigned char *) 0xb8000;
    videoram[0] = 65; /* character 'A' */
    videoram[1] = 0x07; /* light grey (7) on black (0). */
}
and I expect that the character A should appear in the upper left corner of the display. However, all I'm getting is a light grey dot. I get the A character if I rewrite the code so that it looks like this:

Code: Select all

void main(void)
{
    unsigned char *videoram = (unsigned char *) 0xb8000;
    videoram[1] = 65; /* character 'A' */
    videoram[2] = 0x07; /* light grey (7) on black (0). */
}
All documentation I've seen suggests that I should start on index 0 and not index 1. Is there something wrong here?

Re: Video RAM starts on index 1 instead of index 0

Posted: Sun Sep 18, 2011 11:15 am
by DavidCooper
frost wrote:

Code: Select all

void main(void)
{
    unsigned char *videoram = (unsigned char *) 0xb8000;
    videoram[0] = 65; /* character 'A' */
    videoram[1] = 0x07; /* light grey (7) on black (0). */
}
and I expect that the character A should appear in the upper left corner of the display. However, all I'm getting is a light grey dot.
Interesting problem. What happens if you change the char and colour values? Try writing a longer list of these to the screen and see if a pattern emerges.
I get the A character if I rewrite the code so that it looks like this:

Code: Select all

void main(void)
{
    unsigned char *videoram = (unsigned char *) 0xb8000;
    videoram[1] = 65; /* character 'A' */
    videoram[2] = 0x07; /* light grey (7) on black (0). */
}
And it would be interesting to know if changing the colour value there makes the A change colour.
All documentation I've seen suggests that I should start on index 0 and not index 1. Is there something wrong here?
The documentation is absolutely right. I don't know how your programming language works, so I can't tell whether the bug is in the above or some other bit of your code, but have you tried doing a hex dump to see which screen addresses the values are actually being sent to? That should help you track down the error.

Re: Video RAM starts on index 1 instead of index 0

Posted: Sun Sep 18, 2011 12:21 pm
by pdurlej
This problem is interesting indeed. What does you code do after main returns? Can you please post a register dump after you code reaches the final point? This should include the full contents of the segment registers. Can you produce a disassembly of the function main with objdump(1) -d and post it here?

Re: Video RAM starts on index 1 instead of index 0

Posted: Sun Sep 18, 2011 1:49 pm
by frost
DavidCooper wrote: Interesting problem. What happens if you change the char and colour values? Try writing a longer list of these to the screen and see if a pattern emerges.
...
And it would be interesting to know if changing the colour value there makes the A change colour.
I've tried a few other things like

Code: Select all

void main(void)
{
    unsigned char *videoram = (unsigned char *) 0xb8000;
    videoram[1] = 'A';
    videoram[2] = 0x07;
    videoram[3] = 'B';
    videoram[4] = 0x16;
    videoram[5] = 'C';
    videoram[6] = 0x25;
}
and it shows up correctly while starting at zero just makes the output garbled.

Re: Video RAM starts on index 1 instead of index 0

Posted: Sun Sep 18, 2011 2:03 pm
by frost
acek wrote:This problem is interesting indeed. What does you code do after main returns?
It then enters a loop where it does nothing except hlt.
acek wrote:Can you please post a register dump after you code reaches the final point? This should include the full contents of the segment registers.

Code: Select all

EAX=000b8000 EBX=80000011 ECX=c0000080 EDX=00000000
ESI=00000000 EDI=0000d000 EBP=00000000 ESP=00003000
EIP=000010c5 EFL=00000016 [----AP-] CPL=0 II=0 A20=1 SMM=0 HLT=1
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00003000 00000017
IDT=     00000000 000003ff
CR0=00000011 CR2=00000000 CR3=00009000 CR4=f000ff53
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=00000001 CCD=000b8000 CCO=ADDL    
EFER=0000000000000100
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 FPR1=0000000000000000
FPR2=0000000000000000 FPR3=0000000000000000
FPR4=0000000000000000 FPR5=0000000000000000
FPR6=0000000000000000 FPR7=0000000000000000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
acek wrote:Can you produce a disassembly of the function main with objdump(1) -d and post it here?

Code: Select all

00000000000010c8 <main>:
    10c8:	55                   	push   %rbp
    10c9:	48 89 e5             	mov    %rsp,%rbp
    10cc:	48 83 ec 10          	sub    $0x10,%rsp
    10d0:	48 c7 45 f8 00 80 0b 	movq   $0xb8000,-0x8(%rbp)
    10d7:	00 
    10d8:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
    10dc:	c6 00 41             	movb   $0x41,(%rax)
    10df:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
    10e3:	48 83 c0 01          	add    $0x1,%rax
    10e7:	c6 00 07             	movb   $0x7,(%rax)
    10ea:	c9                   	leaveq 
    10eb:	c3                   	retq   
My assembly is a bit shaky but that looks right to me.

Re: Video RAM starts on index 1 instead of index 0

Posted: Sun Sep 18, 2011 2:26 pm
by Combuster
It looks like you're running 64-bit code in 32-bit mode, i.e. you used the wrong compiler. The result is that due to the difference in mode, mov byte [rax], 65 gets decoded like dec/inc eax; mov byte [eax], 65 instead, yielding the result you see.

Solution: GCC Cross-Compiler (which is actually mentioned in the tutorial itself).

Re: Video RAM starts on index 1 instead of index 0

Posted: Sun Sep 18, 2011 2:31 pm
by turdus
Text mode video ram contains words. Lower byte encodes the char, upper byte encodes attributes. So it's totally wrong if you draw a char at 1, and it shows up instead of changing the colour of first character on screen. The bug will be in your code or linker script I bet.

What interesting, objdump shows your main code starts at 00000000000010c8
but the regdump says the instruction pointer is at 000010c5,
three bytes BEFORE your code. Something is totally wrong with your code.

Re: Video RAM starts on index 1 instead of index 0

Posted: Sun Sep 18, 2011 3:28 pm
by frost
turdus wrote:Text mode video ram contains words. Lower byte encodes the char, upper byte encodes attributes. So it's totally wrong if you draw a char at 1, and it shows up instead of changing the colour of first character on screen. The bug will be in your code or linker script I bet.
That sounds reasonable. This is what my code looks like:

Code: Select all

        .set ALIGN, 1 << 0
        .set MEM_INFO, 1 << 1
        .set KLUDGE, 1 << 16
        .set MAGIC, 0x1BADB002
        .set FLAGS, ALIGN | MEM_INFO | KLUDGE
        .set CHECKSUM, -(MAGIC + FLAGS)
        .set STACKSIZE, 4096
        .set PAGE_TABLE, 0x9000
        .set PAGE_PRESENT, 1<<0
        .set PAGE_WRITE, 1<<1

        .text
        .code32

        .align 4
mb_header:
        .long MAGIC
        .long FLAGS
        .long CHECKSUM
        .long mb_header
        .long text
        .long data_end
        .long kernel_end
        .long start

        .global start
start:
        cld

        movl $(stack + STACKSIZE), %esp

        movl $PAGE_TABLE, %edi
        movl $0x1000, %ecx
        xorl %eax, %eax
        rep stosw
        
        leal PAGE_TABLE+0x1000, %eax
        orl $(PAGE_PRESENT | PAGE_WRITE), %eax
        movl %eax, PAGE_TABLE

        leal PAGE_TABLE+0x2000, %eax
        orl $(PAGE_PRESENT | PAGE_WRITE), %eax
        movl %eax, PAGE_TABLE+0x1000

        leal PAGE_TABLE+0x3000, %eax
        orl $(PAGE_PRESENT | PAGE_WRITE), %eax
        movl %eax, PAGE_TABLE+0x2000

        leal PAGE_TABLE+0x3000, %edi
        movl $(PAGE_PRESENT | PAGE_WRITE), %eax
1:
        movl %eax, (%edi)
        addl $0x1000, %eax
        addl $8, %edi
        cmp $0x200000, %eax
        jb 1b

        movl 0b10100000, %eax
        movl %eax, %cr4

        movl $PAGE_TABLE, %edx
        movl %edx, %cr3

        movl $0xC0000080, %ecx
        rdmsr
        orl $0x00000100, %eax
        wrmsr

        movl %cr0, %ebx
        orl $0x80000001, %ebx
        movl %ebx, %cr0

        lgdt gdtinfo

        ljmp $0x8, $start64

        .code64
start64: 
        movw $0x10, %ax
        movw %ax, %ds
        movw %ax, %es
        movw %ax, %fs
        movw %ax, %gs

        call main
        
hang:
        hlt
        jmp hang

        .data

stack:
        .space STACKSIZE, 0

gdt:
        .byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
        .byte 0xff, 0xff, 0x00, 0x00, 0x00, 0x9a, 0xcf, 0x00
        .byte 0xff, 0xff, 0x00, 0x00, 0x00, 0x92, 0xcf, 0x00
gdtinfo:       
        .word .-gdt-1
        .long gdt
        .long 0
And this is the linker script:

Code: Select all

OUTPUT_FORMAT("elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(start)

SECTIONS {
    . = 0x200;

    kernel_start = .;

    .text ALIGN(4096) : AT( ADDR(.text) ) {
        text = .;
        *(.text)
        *(.rodata)
        text_end = .;
    }

    .data ALIGN(4096) : AT( ADDR(.data) ) {
        data = .;
        *(.data)
        data_end = .;
    }

    .bss ALIGN(4096) : AT( ADDR(.bss) ) {
        bss = .;
        *(.bss)
        bss_end = .;
    }

    kernel_end = .;
}
turdus wrote:What interesting, objdump shows your main code starts at 00000000000010c8
but the regdump says the instruction pointer is at 000010c5,
three bytes BEFORE your code. Something is totally wrong with your code.
The objdump was only for the main function. I have some initialization code located before it that enters long mode and calls the function. After main have returned it stays there in a loop where it does nothing. It's probably best if I post the complete objdump.

Code: Select all

kernel:     file format elf64-x86-64


Disassembly of section .text:

0000000000001000 <text>:
    1000:	02 b0 ad 1b 03 00    	add    0x31bad(%rax),%dh
    1006:	01 00                	add    %eax,(%rax)
    1008:	fb                   	sti    
    1009:	4f 51                	rex.WRXB push %r9
    100b:	e4 00                	in     $0x0,%al
    100d:	10 00                	adc    %al,(%rax)
    100f:	00 00                	add    %al,(%rax)
    1011:	10 00                	adc    %al,(%rax)
    1013:	00 24 30             	add    %ah,(%rax,%rsi,1)
    1016:	00 00                	add    %al,(%rax)
    1018:	00 40 00             	add    %al,0x0(%rax)
    101b:	00 20                	add    %ah,(%rax)
    101d:	10 00                	adc    %al,(%rax)
	...

0000000000001020 <start>:
    1020:	fc                   	cld    
    1021:	bc 00 30 00 00       	mov    $0x3000,%esp
    1026:	bf 00 90 00 00       	mov    $0x9000,%edi
    102b:	b9 00 10 00 00       	mov    $0x1000,%ecx
    1030:	31 c0                	xor    %eax,%eax
    1032:	66 f3 ab             	rep stos %ax,%es:(%rdi)
    1035:	8d 05 00 a0 00 00    	lea    0xa000(%rip),%eax        # b03b <PAGE_TABLE+0x203b>
    103b:	83 c8 03             	or     $0x3,%eax
    103e:	a3 00 90 00 00 8d 05 	movabs %eax,0xb000058d00009000
    1045:	00 b0 
    1047:	00 00                	add    %al,(%rax)
    1049:	83 c8 03             	or     $0x3,%eax
    104c:	a3 00 a0 00 00 8d 05 	movabs %eax,0xc000058d0000a000
    1053:	00 c0 
    1055:	00 00                	add    %al,(%rax)
    1057:	83 c8 03             	or     $0x3,%eax
    105a:	a3 00 b0 00 00 8d 3d 	movabs %eax,0xc0003d8d0000b000
    1061:	00 c0 
    1063:	00 00                	add    %al,(%rax)
    1065:	b8 03 00 00 00       	mov    $0x3,%eax
    106a:	89 07                	mov    %eax,(%rdi)
    106c:	05 00 10 00 00       	add    $0x1000,%eax
    1071:	83 c7 08             	add    $0x8,%edi
    1074:	3d 00 00 20 00       	cmp    $0x200000,%eax
    1079:	72 ef                	jb     106a <start+0x4a>
    107b:	a1 a0 00 00 00 0f 22 	movabs 0xbae0220f000000a0,%eax
    1082:	e0 ba 
    1084:	00 90 00 00 0f 22    	add    %dl,0x220f0000(%rax)
    108a:	da b9 80 00 00 c0    	fidivrl -0x3fffff80(%rcx)
    1090:	0f 32                	rdmsr  
    1092:	0d 00 01 00 00       	or     $0x100,%eax
    1097:	0f 30                	wrmsr  
    1099:	0f 20 c3             	mov    %cr0,%rbx
    109c:	81 cb 01 00 00 80    	or     $0x80000001,%ebx
    10a2:	0f 22 c3             	mov    %rbx,%cr0
    10a5:	0f 01 15 18 30 00 00 	lgdt   0x3018(%rip)        # 40c4 <kernel_end+0xc4>
    10ac:	ea                   	(bad)  
    10ad:	b3 10                	mov    $0x10,%bl
    10af:	00 00                	add    %al,(%rax)
    10b1:	08 00                	or     %al,(%rax)

00000000000010b3 <start64>:
    10b3:	66 b8 10 00          	mov    $0x10,%ax
    10b7:	8e d8                	mov    %eax,%ds
    10b9:	8e c0                	mov    %eax,%es
    10bb:	8e e0                	mov    %eax,%fs
    10bd:	8e e8                	mov    %eax,%gs
    10bf:	e8 04 00 00 00       	callq  10c8 <main>

00000000000010c4 <hang>:
    10c4:	f4                   	hlt    
    10c5:	eb fd                	jmp    10c4 <hang>
	...

00000000000010c8 <main>:
    10c8:	55                   	push   %rbp
    10c9:	48 89 e5             	mov    %rsp,%rbp
    10cc:	48 83 ec 10          	sub    $0x10,%rsp
    10d0:	48 c7 45 f8 00 80 0b 	movq   $0xb8000,-0x8(%rbp)
    10d7:	00 
    10d8:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
    10dc:	c6 00 41             	movb   $0x41,(%rax)
    10df:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
    10e3:	48 83 c0 01          	add    $0x1,%rax
    10e7:	c6 00 07             	movb   $0x7,(%rax)
    10ea:	c9                   	leaveq 
    10eb:	c3                   	retq   

Re: Video RAM starts on index 1 instead of index 0

Posted: Sun Sep 18, 2011 7:41 pm
by ~
Combuster wrote:It looks like you're running 64-bit code in 32-bit mode, i.e. you used the wrong compiler. The result is that due to the difference in mode, mov byte [rax], 65 gets decoded like dec/inc eax; mov byte [eax], 65 instead, yielding the result you see.

Solution: GCC Cross-Compiler (which is actually mentioned in the tutorial itself).
@frost:
I think this is the answer.

It means that your source code isn't the problem; instead the problem are the tools (compiler).

You need to use another one, one that doesn't generate 64-bit code when you intend to get and run 32-bit code.

By now, your A character doesn't show where it should, because your pointer is increased or decreased by 1 by the machine code your compiler is generating.

You could try HIEW or a similar disassembly tool to see more easily how instructions look in 32-bit mode disassembly of your kernel.

It happens because of a 64-bit prefix, which coincidentally corresponds to INC (advance pointer in 1) or DEC (decrease pointer in 1) is present just before you put the A character.

Thus, you see that you are off 1.

Re: Video RAM starts on index 1 instead of index 0

Posted: Mon Sep 19, 2011 4:02 am
by Combuster
frost, setting the EFER register, wrote: movl $0xC0000080, %ecx
rdmsr
orl $0x00000100, %eax
wrmsr
That's not quite part of the Bare Bones tutorial which you said you followed. You might want to be more specific next time to avoid confusion.

Anyway, if your actual goal is to adapt the tutorial to run in long mode instead, you should realize that setting LME+PG only enters compatibility mode. The next thing you do is still jump to a 32-bit code segment (whose definition you probably got from Barebones) rather than a 64-bit code segment. Either fix your GDT, or as said previously, actually use a 32-bit compiler). But at least stop mixing 64 and 32 bits because it doesn't quite work :wink:

Re: Video RAM starts on index 1 instead of index 0

Posted: Mon Sep 19, 2011 1:29 pm
by DavidCooper
frost wrote:

Code: Select all

00000000000010c8 <main>:
    10c8:	55                   	push   %rbp
    10c9:	48 89 e5             	mov    %rsp,%rbp
    10cc:	48 83 ec 10          	sub    $0x10,%rsp
    10d0:	48 c7 45 f8 00 80 0b 	movq   $0xb8000,-0x8(%rbp)
    10d7:	00 
    10d8:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
    10dc:	c6 00 41             	movb   $0x41,(%rax)
    10df:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
    10e3:	48 83 c0 01          	add    $0x1,%rax
    10e7:	c6 00 07             	movb   $0x7,(%rax)
    10ea:	c9                   	leaveq 
    10eb:	c3                   	retq   
My assembly is a bit shaky but that looks right to me.
It looks right whether it's run as 64 or 32 bit code, so the problem is elsewhere. All the 48s become dec eax instructions if you run it as 32 bit code, but they have no impact on the program as eax is reloaded each time immediately before sending the 65 and later the 7 to screen memory. The address they're sent to should be b8000 the first time and b8001 the next, and there's no sign of 1 being added to it anywhere before the 65 is sent to the screen. This leaves me to wonder, what kind of mechanism is capable of pushing the whole of screen memory out of place by one byte?

Edit: no, I've missed something - if it's run as 32-bit code, the A is actually written to the right place, but the 7 is then written on top of it because of a dec eax just before 1 is added to eax.

Edit 2: and if the compiler uses the same kind of code for all subsequent chars and colours, it will (in 32-bit mode) run a dec eax every time immediately before adding the offset to eax to send the char/colour value to, so only the first one sent (the A) gets sent to the right address, while all the rest end up one place short of their intended destination.