Printing To Screen: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
(→‎Printing Integers: '''If''' we provide example code, we could just as well go the full distance. ;-))
(→‎Printf: The explanations were not enough to base a stdarg.h implementation upon them, factually not quite correct, and as such probably more confusing than helpful.)
Line 105: Line 105:
== Printf ==
== Printf ==


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.
If you're working with C, you may want to print any number of arguments, like <tt>printf()</tt> does. For this, you need to handle variable argument lists. Looking at the <tt>stdarg.h</tt> file from other operating systems (e.g. Linux 0.1), you might be a bit confused by the macro definitions in that file, as they are basically black magic depending on the C calling conventions. As such, they are not exactly portable.


The good news are that <tt>stdarg.h</tt> is part of a freestanding implementation, so you can #include it even from your kernel source files. Under [[GCC]], the following simple implementation uses the gcc's built-in functionality to do all the work for you:
<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.


The good news are that stdagr.h is part of the required features for freestanding implementations, so you can #include it even from your kernel source files.

'''Example:'''

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. This approach however only works on x86 and also depends on the calling convention. Some calling conventions like fastcall, or on other architectures like x86-64 this approach doesn't work because the parameters are passed in registers and/or stack. Under [[GCC]], the following simple implementation uses the gcc's built-in functionality to do all the work for you:
<pre>
<pre>
#define va_start(v,l) __builtin_va_start(v,l)
#define va_start(v,l) __builtin_va_start(v,l)
Line 124: Line 117:
</pre>
</pre>


An implementation of <tt>stdarg.h</tt> and <tt>printf()</tt> is available from geezer/osd ([http://my.execpc.com/~geezer/osd/code/inc/stdarg.h stdarg.h], [http://my.execpc.com/~geezer/osd/code/inc/_printf.h _printf.h], [http://my.execpc.com/~geezer/osd/code/tinylib/stdio/doprintf.c doprintf.c]).
Not using those, <tt>va_arg()</tt> requires 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

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.


Also, Solar's public domain c library has a starg.h implementation as well.
Solar's [http://pdclib.rootdirectory.de Public Domain C Library] has those implementations as well.


== Troubleshooting ==
== Troubleshooting ==

Revision as of 09:59, 21 February 2012

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.

This is quite easy to do, 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 the screen. One is the ASCII code byte and the other the attribute byte. so HeLlo is stored as

0x000b8000: 'H', colourforH
0x000b8002: 'e', colourfore
0x000b8004: 'L', colourforL
0x000b8006: 'l', colourforl
0x000b0008: 'o', colourforo

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).

For instance, using 0x00 as attribute 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 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.

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.

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;
		string++;
		video++;
		*video=colour;
		video++;
	}
}

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

Printing Integers

Just like in any environment: convert the number into a string, and then print the string. 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:

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

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')

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;
}

(see more on the forum.)

Printf

If you're working with C, you may want to print any number of arguments, like printf() does. For this, you need to handle variable argument lists. Looking at the stdarg.h file from other operating systems (e.g. Linux 0.1), you might be a bit confused by the macro definitions in that file, as they are basically black magic depending on the C calling conventions. As such, they are not exactly portable.

The good news are that stdarg.h is part of a freestanding implementation, so you can #include it even from your kernel source files. Under GCC, the following simple implementation uses the gcc's built-in functionality to do all the work for you:

#define va_start(v,l) __builtin_va_start(v,l)
#define va_arg(v,l)   __builtin_va_arg(v,l)
#define va_end(v)     __builtin_va_end(v)
#define va_copy(d,s)  __builtin_va_copy(d,s)
typedef __builtin_va_list va_list;

An implementation of stdarg.h and printf() is available from geezer/osd (stdarg.h, _printf.h, doprintf.c).

Solar's Public Domain C Library has those implementations as well.

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 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)

Another common mistake is to, by following numerous tutorials spread across the net for example, 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 in Real Mode

It's real easy...

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 (0xb8000 instead of 0xb0000)

Printing a Character

While in Protected Mode, try a simple command like

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

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

mov [0xb8000], 0x07690748

and GAS-guys will have

movl $0x07690748,0xb8000

Missing Strings

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

kprint("Hello World");

and that no "Hello World" string appear in your kernel.bin (or whatever), don't search any further