Our Goal

This tutorial is irrelative to operating system, but these functions are widely used in our later tutorials. In general, they are used as substitutions of C standard library functions. If you are not interested in implementations of these functions, just note kprintf works likes printf in C and print_c prints one character on screen with given background and foreground colors. It is safe for you to skip this tutorial.

Download source code


printf is a programmer's good friend, so for printing strings, numbers etc on screen, I am going to implement a function works like printf for output instead of messing with B8000.

I'm not going to implement a fully functioning printf, for Skelix we just need it to print strings, numbers in hexadecimal or binary format, positive integers and characters. The most important feature is it must be able to accept variable length arguments.

By default, when a function like func(int arg1, int arg2, int arg3) is called, its assembly likes this,

pushl arg3

pushl arg2

pushl arg1

call func

Arguments are pushed one by one from right to left in arguments list, so if we want to get more arguments then just dig the stack deeper. How can we know how many arguments are there? The answer is how many %X stuff in format string, how many arguments we have to parse.

In 32-bit mode, all date types which byte length smaller than a integer will be pushed as an integer. That implies even a one byte character occupies 4 bytes in stack We have to be sure we parse the argument correctly.

We are going to let kprintf accept arguments in this format kprintf(color, format string, arguments...), the first argument defines what fg/bg color combination for output. There are some macros we are going to use for parsing stack, if you are familiar with C, then you must be familiar with these macros.


#define args_list char *

This macro used in kprintf to convert the stack space to a character stream for further processing.

#define _arg_stack_size(type)   \


The macro up rounds the argument size to calculate how many 4-byte it occupies in stack.

#define args_start(ap, fmt) do {        \

ap = (char *)((unsigned int)&fmt + _arg_stack_size(&fmt));      \

while (0)

Arguments are after format string, or I should say on the top of fmt in stack, this macro gets the starting address of those arguments.

#define args_end(ap)

Doesn't do anything at this moment

#define args_next(ap, type) (((type *)(ap+=_arg_stack_size(type)))[-1])

Gets the address of next argument.

static char buf[1024] = {-1};

static int ptr = -1;

We put all characters are going to be printed into a buffer and make a pointer point to the next available place.

/* valid base: 2, 8, 10 */

static void

parse_num(unsigned int value, unsigned int base) {

        unsigned int n = value / base;

        int r = value % base;

        if (r < 0) {

                r += base;



        if (value >= base)

                parse_num(n, base);

        buf[ptr++] = "0123456789"[r];



static void

parse_hex(unsigned int value) {

        int i = 8;

        while (i-- > 0) {

                buf[ptr++] = "0123456789abcdef"[(value>>(i*4))&0xf];



These two functions parse the given value into different radix.

/* %s, %c, %x, %d, %% */


kprintf(enum KP_LEVEL kl, const char *fmt, ...) {

        int i = 0;

        char *s;

        /* must be the same size as enum KP_LEVEL */

        struct KPC_STRUCT {

                COLOUR fg;

                COLOUR bg;

        } KPL[] = {

                {BRIGHT_WHITE, BLACK},

                {YELLOW, RED},


enum KP_LEVEL {KPL_DUMP, KPL_PANIC} is defined in 03/include/kprintf.h, it inicates two color scheme for output, KPL_DUMP prints strings with bright white foreground and black background, KPL_PANIC prints strings with yellow foreground and red background. Those color constants are defined in 03/include/scr.h, they are going to be introduced later.

        args_list args;

        args_start(args, fmt);


        ptr = 0;


        for (; fmt[i]; ++i) {

                if ((fmt[i]!='%') && (fmt[i]!='\\')) {

                        buf[ptr++] = fmt[i];


                } else if (fmt[i] == '\\') {

                        /* \a \b \t \n \v \f \r \\ */

                        switch (fmt[++i]) {

                        case 'a': buf[ptr++] = '\a'; break;

                        case 'b': buf[ptr++] = '\b'; break;

                        case 't': buf[ptr++] = '\t'; break;

                        case 'n': buf[ptr++] = '\n'; break;

                        case 'r': buf[ptr++] = '\r'; break;

                        case '\\':buf[ptr++] = '\\'; break;




We accept those escape sequence like printf.

                /* fmt[i] == '%' */

                switch (fmt[++i]) {

                case 's':

                        s = (char *)args_next(args, char *);

                        while (*s)

                                buf[ptr++] = *s++;


                case 'c':

                        /* why is int?? */

                        buf[ptr++] = (char)args_next(args, int);


                case 'x':

                        parse_hex((unsigned long)args_next(args, unsigned long));


                case 'd':

                        parse_num((unsigned long)args_next(args, unsigned long), 10);


                case '%':

                        buf[ptr++] = '%';



                        buf[ptr++] = fmt[i];




        buf[ptr] = '\0';

Puts an trailing \0 in buffer.


        for (i=0; i<ptr; ++i)

                print_c(buf[i], KPL[kl].fg, KPL[kl].bg);


Prints all characters in buffer on screen with function print_c.


Before we go any further, there is a problem might occur depends on you complier version. Even with -nostdlib option, based on GCC manual, "The compiler may generate calls to memcmp, memset and memcpy for System V (and ISO C) environments or to bcopy and bzero for BSD environment." GCC might give you some "can't find memcpy" error etc. I didn't have this problem before when I was using my old version GCC, but it happens now. Wee have to implement these functions by ourselves.

/* result is currect, even when both area overlap */


bcopy(const void *src, void *dest, unsigned int n) {

        const char *s = (const char *)src;

        char *d = (char *)dest;

        if (s <= d)

                for (; n>0; --n)

                        d[n-1] = s[n-1];


                for (; n>0; --n)

                        *d++ = *s++;




bzero(void *dest, unsigned int n) {

        memset(dest, 0, n);



void *

memcpy(void *dest, const void *src, unsigned int n) {

        bcopy(src, dest, n);

        return dest;



void *

memset(void *dest, int c, unsigned int n) {

        char *d = (char *)dest;

        for (; n>0; --n)

                *d++ = (char)c;

        return dest;




memcmp(const void *s1, const void *s2, unsigned int n) {

        const char *s3 = (const char *)s1;

        const char *s4 = (const char *)s2;

        for (; n>0; --n) {

                if (*s3 > *s4)

                        return 1;

                else if (*s3 < *s4)

                        return -1;




        return 0;




strcmp(const char *s1, const char *s2) {

        while (*s1 && *s2) {

                int r = *s1++ - *s2++;

                if (r)

                        return r;


        return (*s1)?1:-1;



char *

strcpy(char *dest, const char *src) {

        char *p = dest;

        while ( (*dest++ = *src++))


        *dest = 0;

        return p;



unsigned int

strlen(const char *s) {

        unsigned int n = 0;

        while (*s++)


        return n;


Feel free to use my code. Please contact me if you have any questions.