0

I am trying to develop my own kernel which I am booting to with GRUB.

In order to display strings with variables, I have started to develop my own printf(char*, ...) function.

Here is the kernel.cpp (thanks to Write Your Own Operating System YouTube tutorial for the main loading part (printf is my code entirely))

#include <stdarg.h>


#define VGA_BLACK 0
#define VGA_BLUE 1
#define VGA_GREEN 2
#define VGA_CYAN 3
#define VGA_RED 4
#define VGA_MAGENTA 5
#define VGA_BROWN 6
#define VGA_GREY 7
#define VGA_BRIGHT 8

#define VGA_DEFAULT_FG VGA_GREEN
#define VGA_DEFAULT_BG VGA_BLACK

volatile char* videoMemory = (volatile char*)0xB8000;
unsigned int color = ((VGA_DEFAULT_BG<<4) | (VGA_DEFAULT_FG & 0x0F));

void putchar(char c){
  *videoMemory++ = c;
  *videoMemory++ = color;
}

void printInt(int number){
  if(number <= 0){
    putchar('0');
    return;
  }
  int diget = number/10;
  char c = diget + 48;
  putchar(c);
  int newNum = diget%10;
  printInt(newNum);
}

int strlen(char* str){
  int len = 0;
  for(int i = 0; str[i] != '\0'; i++){
    len++;
  }
  return len;
}

void printf(char* msg, ...){
  va_list args;
  va_start(args, msg);
  for(int i = 0; i < strlen(msg); i++){
    if(msg[i] == '%'){
      i++;
      if(msg[i] == 'd'){
        i++;
        int res = va_arg(args, int);
        printInt(res);
      } else if(msg[i] == 's'){
        i++;
        char* c;
        do{
          c = va_arg(args, char*);

          putchar(*c);
        } while(c != '\n');
      } else {
        putchar('%');
      }
    } else {
      putchar(msg[i]);
    }
  }
  va_end(args);
}


void setColor(char fg, char bg){
  color = ((bg<<4) | (fg & 0x0F));
}


typedef void (*constructor)();
extern "C" constructor start_ctors;
extern "C" constructor end_ctors;
extern "C" void callConstructors(){
  for(constructor* i = &start_ctors; i != &end_ctors; i++){
    (*i)();
  }
}

extern "C" void kernelMain(void* multibootStructure, unsigned int magicNumber){
  setColor(VGA_GREEN+VGA_BRIGHT, VGA_BLACK);
  printf("Booting %s", "Test System");
  while(true);
}

Result: enter image description here

As you can see, it displays the first character and 'S'.

If I were to change the message to "My Custom Kernel", this is the result:

enter image description here

As you can see here, it again prints the first letter and then 'S'.

Why is this happening? By my understanding, va_args changes char to int, however this does not explain why it only displays the first letter along with an 'S'.

Why is this happening and how can I fix this?

Edit: This has also been compiled with the flags: -m32 -fno-use-cxa-atexit -nostdlib -fno-builtin -fno-rtti -fno-exceptions -fno-leading-underscore

iProgram
  • 6,057
  • 9
  • 39
  • 80
  • Specifier `%s` corresponds to the **single argument** of type `char*`. But you call `c = va_arg(args, char*);` in a `do..while` **loop**. – Tsyvarev Jan 30 '19 at 19:58
  • @Tsyvarev What do you mean? In my printf, if a user (me) wants to display a string, then %s is used. As this is freestanding, I do not have the string type available yet as this is in the standard library? This is why I am trying to loop through the characters array as I can only have `char[] and not std::string`. – iProgram Jan 30 '19 at 20:10
  • If I were not to use the loop, this would only fetch a single character, not the who string. – iProgram Jan 30 '19 at 20:11
  • You need to loop over **characters in the string**, pointed by the single `char*` argument, not to loop over *arguments* (via `va_arg`). – Tsyvarev Jan 30 '19 at 20:21
  • @Tsyvarev Thanks for the info. I've now been able to do it with my strlen function. Didn't think it would have been possible. Feel free to add it as an answer and I will accept it. This method, I was able to use the for loop. – iProgram Jan 30 '19 at 20:31
  • 1
    Yes, the C string is a `char*` pointer, which can be iterated with `c++`, extracting every character via `*c`. End of the string is marked with `*c` equal to `'\0'`. BTW, you already have string `msg` in your code and correctly iterate it. – Tsyvarev Jan 30 '19 at 20:31
  • @Tsyvarev Thanks for that, I understand where I went wrong now! – iProgram Jan 30 '19 at 20:32
  • [This answer](https://stackoverflow.com/a/28581294/3440745) explains how to extract the argument for `%s` specifier. While it doesn't explain how to print extracted argument - `puts()` needs to be implemented too, - but `va_arg` is definitely not a part of `puts` implementation. – Tsyvarev Jan 30 '19 at 20:43
  • Possible duplicate of [How to write my own printf() in C?](https://stackoverflow.com/questions/1735236/how-to-write-my-own-printf-in-c) – Tsyvarev Jan 30 '19 at 20:43
  • @Tsyvarev Thanks for the information. Also regarding the posible duplicate, I do not think it is in this case. I was able to make a slightly modified version on my Mac but when copying it over to my own OS, it did not work. I think this may be due to an implicit conversion from char to string? As this is freestanding, it does not have string enabled. – iProgram Jan 30 '19 at 21:02
  • In the C language aside from null-terminated string there is no other "string", and a character is never converted implicitly to a string. It seems you are confusing about `puts` function used in the referenced answer: this function is NOT the same as `putc` function you already have. These functions has different meaning: `putc` prints a single character, `puts` prints all characters in a string. But you can easily implement `puts` function via calls to `putc` in a loop, thus the answer will work for you. – Tsyvarev Jan 30 '19 at 21:09
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/187607/discussion-between-iprogram-and-tsyvarev). – iProgram Jan 30 '19 at 21:26

0 Answers0