Printing To Screen: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
m (→‎Printing strings: strings should be constants)
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(41 intermediate revisions by 20 users not shown)
Line 1: Line 1:
{{Convert}}

==Basics==
==Basics==


Working on the assumption that you are in [[protected mode]] and not using the [[BIOS]] to do screen writes, you will have to do screen writes direct to "video" memory yourself.
Assuming that you are in [[protected mode]] and not using the [[BIOS]] to write text to screen, you will have write directly to "video" memory.

This is quite easy. The text screen video memory for colour monitors resides at <tt>0xB8000</tt>, and for monochrome monitors it is at address <tt>0xB0000</tt> (see [[Detecting Colour and Monochrome Monitors]] for more information).


Text mode memory takes two bytes for every "character" on screen. One is the ''ASCII code'' byte, the other the ''attribute'' byte. so the text "HeLlo" would be stored as:
This is quite easy to do, the text screen video memory for colour monitors resides at <tt>0xB8000</tt>, and for monochrome monitors it is at address <tt>0xB0000</tt> (see [[Detecting Colour and Monochrome Monitors]] for more information).


Text mode memory takes two bytes for every "character" on the screen. One is the ''ASCII code'' byte and the other the ''attribute'' byte. so <tt>HeLlo</tt> is stored as
<pre>
<pre>
0x000b8000: 'H', colourforH
0x000b8000: 'H', colour_for_H
0x000b8002: 'e', colourfore
0x000b8002: 'e', colour_for_e
0x000b8004: 'L', colourforL
0x000b8004: 'L', colour_for_L
0x000b8006: 'l', colourforl
0x000b8006: 'l', colour_for_l
0x000b0008: 'o', colourforo
0x000b8008: 'o', colour_for_o
</pre>
</pre>


The ''attribute'' byte carries the ''foreground colour'' in its lowest 4 bits and the ''background color'' in its highest 3 bits. The bit #7 's interpretation depends on how you (or the BIOS) configured the hardware (see [[VGA Resources]] for additional info).
The ''attribute'' byte carries the ''foreground colour'' in its lowest 4 bits and the ''background color'' in its highest 3 bits. The interpretation of bit #7 depends on how you (or the BIOS) configured the hardware (see [[VGA Resources]] for additional info).


For instance, using <tt>0x00</tt> as attribute means black-on-black (you'll see nothing). <tt>0x07</tt> is lightgrey-on-black (dos default), <tt>0x1F</tt> is white-on-blue (Win9x's blue-screen-of-death), <tt>0x2a</tt> is for green-monochrome nostalgics.
For instance, using <tt>0x00</tt> as attribute byte means black-on-black (you'll see nothing). <tt>0x07</tt> is lightgrey-on-black (DOS default), <tt>0x1F</tt> is white-on-blue (Win9x's blue-screen-of-death), <tt>0x2a</tt> is for green-monochrome nostalgics.


For colour video cards, you have 32 KB of text video memory to use. Since 80x25 mode does not use all 32 KB (80 x 25 x 2 = 4,000 bytes per screen), you have 8 display pages to use.


When you print to any other page than 0, it will ''not'' appear on screen until that page is ''enabled'' or ''copied'' into the page 0 memory space.
For colour video cards, you have 16kb of text video memory to use, and since 80x25 mode (80x25x2==4000 bytes per screen) does not use all 16kb, you have what is known as 'pages' and in 80x25 screen mode you have 8 display pages to use.


====Color Table====
When you print to any other page than 0, it will ''not'' appear on screen until that page is ''enabled'' or "copied" into the page 0 memory space.


{| {{wikitable}}
==Printing strings==
|-
! Color number
! Color name
! RGB value
! Hex value
|-
| 0
| Black
| 0 0 0
| 00 00 00
|-
| 1
| Blue
| 0 0 170
| 00 00 AA
|-
| 2
| Green
| 0 170 0
| 00 AA 00
|-
| 3
| Cyan
| 0 170 170
| 00 AA AA
|-
| 4
| Red
| 170 0 0
| AA 00 00
|-
| 5
| Purple
| 170 0 170
| AA 00 AA
|-
| 6
| Brown
| 170 85 0
| AA 55 00
|-
| 7
| Gray
| 170 170 170
| AA AA AA
|-
| 8
| Dark Gray
| 85 85 85
| 55 55 55
|-
| 9
| Light Blue
| 85 85 255
| 55 55 FF
|-
| 10
| Light Green
| 85 255 85
| 55 FF 55
|-
| 11
| Light Cyan
| 85 255 255
| 55 FF FF
|-
| 12
| Light Red
| 255 85 85
| FF 55 55
|-
| 13
| Light Purple
| 255 85 255
| FF 55 FF
|-
| 14
| Yellow
| 255 255 85
| FF FF 55
|-
| 15
| White
| 255 255 255
| FF FF FF
|-
|}

==Printing Strings==


If you have a pointer to video memory and want to write a string, here is how you might do it;
If you have a pointer to video memory and want to write a string, here is how you might do it;


<syntaxhighlight lang="c">
<pre>
/* note this example will always write to the top
// note this example will always write to the top
line of the screen */
// line of the screen
void write_string(int colour, const char *string)
void write_string( int colour, const char *string )
{
{
volatile char *video=(volatile char*)0xB8000;
volatile char *video = (volatile char*)0xB8000;
while(*string!=0)
while( *string != 0 )
{
{
*video=*string;
*video++ = *string++;
*video++ = colour;
string++;
}
video++;
*video=colour;
video++;
}
}
}
</syntaxhighlight>
</pre>


This simply cycles through each character in the string, and copies it to the appropriate place in video memory.
==Printing numbers==


For a more advanced print function, you need to store variables for x and y, as the display controller will not print a newline. This involves a switch statement or similar construct.
just like in any environment: convert the number into a string, then print the string.
You also have to test for x>80 or y>25 and in the case of x>80 setting x to 0 and incrementing y, or in the case of y>25 scrolling.
E.g. since 1234 = 4 + 10*3 + 100*2 + 1000*1, if you recursively divide "1234" by ten and use the result of the division, you get all the digits:

==Printing Integers==

Just like in any environment, you repeatedly divide the value by the base, the remainder of the division giving you the least significant digit of the value.

For example, since 1234 = 4 + 3* 10 + 2 * 100 + 1* 1000, if you repeatedly divide "1234" by ten and use the remainder of the division, you get the digits:


<pre>
<pre>
Line 58: Line 150:
</pre>
</pre>


digits to be displayed are '1','2','3','4' ... if you know the numerical value of number%10, you simply have to add this to the character '0' to have the correct character (e.g. '0'+4 == '4')
As this algorithm retrieves the digits in the "wrong" order (last-to-first), you have to either work recursively, or invert the sequence of digits afterwards. If you know the numerical value of <tt>number % 10</tt>, you simply have to add this to the character '0' to have the correct character (e.g. '0'+4 == '4')


Here is an example implementation of the itoa() function (which is not standard, but provided by many libraries):
(see more on [the forum|Forum:7462])


<syntaxhighlight lang="c">
==Printing formatted messages (a la printf)==
char * itoa( int value, char * str, int base )
{
char * rc;
char * ptr;
char * low;
// Check for supported base.
if ( base < 2 || base > 36 )
{
*str = '\0';
return str;
}
rc = ptr = str;
// Set '-' for negative decimals.
if ( value < 0 && base == 10 )
{
*ptr++ = '-';
}
// Remember where the numbers start.
low = ptr;
// The actual conversion.
do
{
// Modulo is negative for negative value. This trick makes abs() unnecessary.
*ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + value % base];
value /= base;
} while ( value );
// Terminating the string.
*ptr-- = '\0';
// Invert the numbers.
while ( low < ptr )
{
char tmp = *low;
*low++ = *ptr;
*ptr-- = tmp;
}
return rc;
}
</syntaxhighlight>
[http://www.strudel.org.uk/itoa/ And here is a shorter one]


== Troubleshooting ==
If you're working with C, you may want to print any number of arguments and you may have looked at the <tt>stdarg.h</tt> file from other Operating Systems (e.g. linux 0.1 and Thix 0.3.x). These macro definitions may be a bit weird to understand as they're basically C voodoo using pointers and casts and sizeof. Beware before porting them to 16 bit systems, though.


=== Nothing is Displayed ===
<tt>va_start()</tt> points to the first variable argument.
<tt>va_arg()</tt> advances the pointer, then evaluates to the previous argument (comma operator).
<tt>va_end()</tt> doesn't do anything.


Keep in mind that this way of writing to video memory will ''only'' work if the screen has been correctly set up for 80x25 video mode (which is mode 03). You can do this either by initializing every VGA register manually, or by calling the ''Set Video Mode'' service of the BIOS Int10h while you're still in real mode (in your bootsector, for instance). Most BIOS's do that initialization for you, but some other (mainly on laptops) do not. Check out [[Ralf Brown's Interrupt List]] for details. Note also that some modes that are reported as "both text & graphic" by mode lists are actually graphic modes with BIOS functions that plot fonts when you call char/message output through Int10h (which means you'll end up with plain graphic mode once in [[Protected Mode]]).
E.g. to implement <tt>va_start()</tt>, you extract the address of the last 'known' argument and advance (yes, it's a <tt>+</tt> since you're rewinding the stack) to the next stack item. The voodoo comes to the fact that things are automatically aligned on 32bits boundary on the stack, even if just 1 byte. Under gcc, the <tt>_builtin_next_arg()</tt> function may help you. Versions 3.x even seem to have <tt>_builtin_va_start()</tt>, <tt>_builtin_va_end()</tt> and <tt>_builtin_va_arg()</tt> so no black magic is required at all.


([[GRUB]] does this setup for you.)
<tt>va_arg()</tt> usually require a bit more black magic since you have two things to do:
* advance to the next item
* return the value of the (previously current) item


Another common mistake, e.g. in numerous tutorials spread across the net, is to link the .text section of your kernel/OS to the wrong memory address. If you don't have memory management in place yet, make sure you're using physical memory locations in the linker script.
You can get [http://my.execpc.com/~geezer/osd/code/inc/stdarg.h stdarg.h], [http://my.execpc.com/~geezer/osd/code/inc/_printf.h _printf.h] and [http://my.execpc.com/~geezer/osd/code/tinylib/stdio/doprintf.c doprintf.c] from geezer/osd.


===Printing a Character===
== Uh ? I get nothing displayed at all ...==


While in Protected Mode, try a simple command like:
Keep in mind that this way of writing to video memory will _only_ work if the screen has been correctly set up for 80x25 video mode (which is mode 03). You can do this either by initializing every VGA register manually or by calling the Set Video Mode service of the BIOS Int10h while you're still in real mode (in your bootsector, for instance). Most BIOS's does that initialization for you, but some other (mainly on laptops) do not. Check out [[Ralf Brown's Interrupt List]] for details. Note also that some modes that are reported as "both text&graphic" by mode lists are actually graphic modes with BIOS functions that plot fonts when you call char/message output through Int10h (which means you'll end up with plain graphic mode once in [[Protected Mode]])

More hints for non-working implementations on [Help! I cannot print to screen !?]

==Troubleshooting==

So, you read everything in [How do I output text to the screen in protected mode?] carefully, but it still doesn't work. Here's a check list with suggestion to find out what's wrong.

=== Can you print in real mode ?===

While still in [[Real Mode]], try to write directly to Video memory. If this doesn't work either you haven't set up the Video mode properly to 0x03 (check out [[RBIL]]) or you're assuming the wrong video memory address (<tt>0xb8000</tt> instead of <tt>0xb0000</tt>)

=== Can you print a single character ?===

While in Protected Mode, try a simple command like


<pre>
<pre>
// C
*((int*)0xb8000)=0x07690748;
*((int*)0xb8000)=0x07690748;
</pre>


// NASM
which should display 'Hi' in grey-on-black on top of your screen. If the previous step worked and not this one, check your paging / segmentation setup correctly maps your assumed video memory address onto 0xB8000 (or 0xB0000). NASM-only developers may use
<pre>
mov [0xb8000], 0x07690748
mov [0xb8000], 0x07690748
</pre>


// GAS
and GAS-guys will have
<pre>
movl $0x07690748,0xb8000
movl $0x07690748,0xb8000
</pre>
</pre>


which should display 'Hi' in grey-on-black on top of your screen. If this does not work, check your paging / segmentation setup correctly maps your assumed video memory address to 0xB8000 (or 0xB0000).
=== Can you find your string in the kernel image ?===


=== Missing Strings ===
That may sound stupid, but it's a common mistake to forget the <tt>.rodata</tt> section in the linker script. <tt>-fwritable-strings</tt> can be a substitute, but still if you had

<pre>
Sometimes printing individual characters works, but printing strings fails. This is usually due to the <tt>.rodata</tt> section missing in the linker script.
kprint("Hello World");

</pre>
Previously, GCC had an option <tt>-fwritable-strings</tt> which could be used as a workaround for this, but it was deprecated in version 3.0 and removed in 4.0 and later, which was released in 2005. Even when the option was available, it was a kludge; the real solution was, and still is, to add <tt>.rodata</tt> to the script.
and that no "Hello World" string appear in your kernel.bin (or whatever), don't search any further

==See Also==
*[[Printing to the screen without a db]]


[[Category:Tutorials]]
[[Category:Video]]
[[Category:Video]]
[[Category:Text UI]]

Latest revision as of 05:42, 9 June 2024

Basics

Assuming that you are in protected mode and not using the BIOS to write text to screen, you will have write directly to "video" memory.

This is quite easy. The text screen video memory for colour monitors resides at 0xB8000, and for monochrome monitors it is at address 0xB0000 (see Detecting Colour and Monochrome Monitors for more information).

Text mode memory takes two bytes for every "character" on screen. One is the ASCII code byte, the other the attribute byte. so the text "HeLlo" would be stored as:

0x000b8000: 'H', colour_for_H
0x000b8002: 'e', colour_for_e
0x000b8004: 'L', colour_for_L
0x000b8006: 'l', colour_for_l
0x000b8008: 'o', colour_for_o

The attribute byte carries the foreground colour in its lowest 4 bits and the background color in its highest 3 bits. The interpretation of bit #7 depends on how you (or the BIOS) configured the hardware (see VGA Resources for additional info).

For instance, using 0x00 as attribute byte means black-on-black (you'll see nothing). 0x07 is lightgrey-on-black (DOS default), 0x1F is white-on-blue (Win9x's blue-screen-of-death), 0x2a is for green-monochrome nostalgics.

For colour video cards, you have 32 KB of text video memory to use. Since 80x25 mode does not use all 32 KB (80 x 25 x 2 = 4,000 bytes per screen), you have 8 display pages to use.

When you print to any other page than 0, it will not appear on screen until that page is enabled or copied into the page 0 memory space.

Color Table

Color number Color name RGB value Hex value
0 Black 0 0 0 00 00 00
1 Blue 0 0 170 00 00 AA
2 Green 0 170 0 00 AA 00
3 Cyan 0 170 170 00 AA AA
4 Red 170 0 0 AA 00 00
5 Purple 170 0 170 AA 00 AA
6 Brown 170 85 0 AA 55 00
7 Gray 170 170 170 AA AA AA
8 Dark Gray 85 85 85 55 55 55
9 Light Blue 85 85 255 55 55 FF
10 Light Green 85 255 85 55 FF 55
11 Light Cyan 85 255 255 55 FF FF
12 Light Red 255 85 85 FF 55 55
13 Light Purple 255 85 255 FF 55 FF
14 Yellow 255 255 85 FF FF 55
15 White 255 255 255 FF FF FF

Printing Strings

If you have a pointer to video memory and want to write a string, here is how you might do it;

// note this example will always write to the top
// line of the screen
void write_string( int colour, const char *string )
{
    volatile char *video = (volatile char*)0xB8000;
    while( *string != 0 )
    {
        *video++ = *string++;
        *video++ = colour;
    }
}

This simply cycles through each character in the string, and copies it to the appropriate place in video memory.

For a more advanced print function, you need to store variables for x and y, as the display controller will not print a newline. This involves a switch statement or similar construct. You also have to test for x>80 or y>25 and in the case of x>80 setting x to 0 and incrementing y, or in the case of y>25 scrolling.

Printing Integers

Just like in any environment, you repeatedly divide the value by the base, the remainder of the division giving you the least significant digit of the value.

For example, since 1234 = 4 + 3* 10 + 2 * 100 + 1* 1000, if you repeatedly divide "1234" by ten and use the remainder of the division, you get the digits:

1234 = 123*10 + 4
123 = 12*10 + 3
12 = 1*10 + 2
1 = 1

As this algorithm retrieves the digits in the "wrong" order (last-to-first), you have to either work recursively, or invert the sequence of digits afterwards. If you know the numerical value of number % 10, you simply have to add this to the character '0' to have the correct character (e.g. '0'+4 == '4')

Here is an example implementation of the itoa() function (which is not standard, but provided by many libraries):

char * itoa( int value, char * str, int base )
{
    char * rc;
    char * ptr;
    char * low;
    // Check for supported base.
    if ( base < 2 || base > 36 )
    {
        *str = '\0';
        return str;
    }
    rc = ptr = str;
    // Set '-' for negative decimals.
    if ( value < 0 && base == 10 )
    {
        *ptr++ = '-';
    }
    // Remember where the numbers start.
    low = ptr;
    // The actual conversion.
    do
    {
        // Modulo is negative for negative value. This trick makes abs() unnecessary.
        *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + value % base];
        value /= base;
    } while ( value );
    // Terminating the string.
    *ptr-- = '\0';
    // Invert the numbers.
    while ( low < ptr )
    {
        char tmp = *low;
        *low++ = *ptr;
        *ptr-- = tmp;
    }
    return rc;
}

And here is a shorter one

Troubleshooting

Nothing is Displayed

Keep in mind that this way of writing to video memory will only work if the screen has been correctly set up for 80x25 video mode (which is mode 03). You can do this either by initializing every VGA register manually, or by calling the Set Video Mode service of the BIOS Int10h while you're still in real mode (in your bootsector, for instance). Most BIOS's do that initialization for you, but some other (mainly on laptops) do not. Check out Ralf Brown's Interrupt List for details. Note also that some modes that are reported as "both text & graphic" by mode lists are actually graphic modes with BIOS functions that plot fonts when you call char/message output through Int10h (which means you'll end up with plain graphic mode once in Protected Mode).

(GRUB does this setup for you.)

Another common mistake, e.g. in numerous tutorials spread across the net, is to link the .text section of your kernel/OS to the wrong memory address. If you don't have memory management in place yet, make sure you're using physical memory locations in the linker script.

Printing a Character

While in Protected Mode, try a simple command like:

// C
*((int*)0xb8000)=0x07690748;

// NASM
mov [0xb8000], 0x07690748

// GAS
movl $0x07690748,0xb8000

which should display 'Hi' in grey-on-black on top of your screen. If this does not work, check your paging / segmentation setup correctly maps your assumed video memory address to 0xB8000 (or 0xB0000).

Missing Strings

Sometimes printing individual characters works, but printing strings fails. This is usually due to the .rodata section missing in the linker script.

Previously, GCC had an option -fwritable-strings which could be used as a workaround for this, but it was deprecated in version 3.0 and removed in 4.0 and later, which was released in 2005. Even when the option was available, it was a kludge; the real solution was, and still is, to add .rodata to the script.

See Also