0

I was making an os, or atleast trying to, but I stumbled upon a problem. While trying to iterate over a string to convert to char to print to screen, the returned char seemed to be empty!(I am actually new to os development); Here is the code snippet:

int offset = 0;

void clear_screen() {
    unsigned char * video = 0xB8000;
    for(int i = 0; i < 2000; i+=2){
        video[i] = ' ';
    }
}

void printc(char c) {
    unsigned char * video = 0xB8000;
    video[offset] = c;
    video[offset+1] = 0x03;
    offset += 2;
}

void print(unsigned char *string) {
    char * sus = '\0';
    uint32 i = 0;
    printc('|');
    sus[0] = 'a';
    printc(sus[0]);  //this prints "a" correctly
    string[i] = 'c';
    while (string[i] != '\0') {
        printc(string[i]);   //this while loop is only called once 
        i++;                 //it prints " " only once and exits
    }
    printc('|');
}

int bootup(void)
{
    clear_screen();

    // printc('h');
    // printc('e');
    // printc('l');                     /* These work */
    // printc('l');
    // printc('o');

    print("hello"); //this doesn't

    return 1;

}

Output that it prints:

|a |

Thanks in advance!!

edit

New print function

void print(unsigned char *string) {
    uint32 i = 0;
    printc('|');
    while (string[i] != '\0') {
        printc('i');  //not printed
        printc(string[i]);
        i++;
    }
    printc('|');
}

still does not work

edit 2 updated the code as per @lundin's advice

int offset = 0;

void clear_screen() {
    unsigned char * video = (unsigned char *)0xB8000;
    for(int i = 0; i < 2000; i+=2){
        video[i] = ' ';
    }
}

void printc(char c) {
    unsigned char * video = (unsigned char *)0xB8000;
    video[offset] = c;
    video[offset+1] = 0x03;
    offset += 2;
}

void print(const char *string) {
    int i = 0;
    printc('|');
    while (string[i] != '\0') {
        printc('i');
        printc(string[i]);
        i++;
    }
    printc('|');
}

int bootup(void)
{
    clear_screen();
    // printc('h');
    // printc('e');
    // printc('l');
    // printc('l');
    // printc('o');
    print("hello");
    return 1;

}

stack:

init_lm:
    mov ax, 0x10
    mov fs, ax          ;other segments are ignored
    mov gs, ax

    mov rbp, 0x90000    ;set up stack
    mov rsp, rbp

    ;Load kernel from disk
    xor ebx, ebx        ;upper 2 bytes above bh in ebx is for cylinder = 0x0
    mov bl, 0x2         ;read from 2nd sectors
    mov bh, 0x0         ;head
    mov ch, 1           ;read 1 sector
    mov rdi, KERNEL_ADDRESS
    call ata_chs_read


    jmp KERNEL_ADDRESS

    jmp $
avgkid
  • 3
  • 3
  • 1
    `char * sus = '\0';` is wrong, `sus` is a pointer to `char`. Because `'\0'` is equals to 0, `sus` points to 0 which will be `NULL` (unless your using an "esoteric" compiler) – Pablo Jan 24 '22 at 04:25
  • Also you need to allocate memory in order to write to it, you are not doing that either – Pablo Jan 24 '22 at 04:28
  • your program is full of undefined behaviours. For example in print the string variable points to a string literal that in most cases is read only memory, yet you are trying to write to it, since it's your own OS, you are corrupting memory all over the place, hence the strange behaviours – Pablo Jan 24 '22 at 04:46
  • i have removed the statement `string[i] = 'c'`, still it is not working. I only put that to debug and that statement was printing `c`. – avgkid Jan 24 '22 at 04:47
  • what is this magic `0x03`? – Pablo Jan 24 '22 at 04:58
  • the `0x03` symbol is for the color, in this case cyan over black – avgkid Jan 24 '22 at 05:00
  • i think that the string parameter is empty because the condition for the while loop is `string[i] != '\0'` and the while loop is not being called – avgkid Jan 24 '22 at 05:05
  • but I don't understand why the string parameter is being read as empty, even the printc parameter `char c` is being read – avgkid Jan 24 '22 at 05:12
  • no, not working, neither of them, the while loop condition is not being satisfied – avgkid Jan 24 '22 at 05:19
  • What happens if you even more simpler just call `printc('x');` 3 times in `print()`, ignoring the provided string? – the busybee Jan 24 '22 at 07:27
  • it works, just not inside the while loop – avgkid Jan 24 '22 at 09:28

3 Answers3

0

Your program has undefined behavior since it contains multiple lines that aren't valid C. You will have gotten compiler messages about those lines.

  • unsigned char * video = 0xB8000; etc is not valid C, you need an explicit cast. "Pointer from integer/integer from pointer without a cast" issues
  • Similarly, char * sus = '\0'; is also not valid C. You are trying to assign a pointer to a single character, which doesn't make sense. String handling beginner FAQ here: Common string handling pitfalls in C programming. It also addresses memory allocation basics.
  • sus[0] = 'a'; etc here you have wildly undefined behavior since sus isn't pointing at valid memory.
  • In case you are actually trying to access physical memory addresses, this isn't the correct way to do so. You need volatile qualified pointers. See How to access a hardware register from firmware? (In your case it probably isn't a register but everything from that link still applies - how to use hex constants etc.)
  • EDIT: void print(unsigned char *string) ... string[i] = 'c'; is also wrong. First of all you are passing a char* which is not necessarily compatible with unsigned char*. Then you shouldn't modify the passed string from inside a function called print, that doesn't make sense. This should have been const char* string to prevent such bugs. As it stands you are passing a string literal to this function and then try to modify it - that is undefined behavior since string literals are read-only.

Assuming gcc or clang, if you wish to block the compiler from generating an executable out of invalid C code, check out What compiler options are recommended for beginners learning C? In your case you also likely need the -ffreestanding option mentioned there.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • I have made the edits that you have mentioned; i casted `0xB8000` as an `unsigned char *` and removed the `sus` variable functionality and the variable itself, though it was just to debug. – avgkid Jan 24 '22 at 09:35
  • but I can't still understand as to why the the iterated string returns an empty `char`, am I iterating it wrongly? – avgkid Jan 24 '22 at 09:37
  • @AkTunes Well I just noticed you actually have yet another bug - you can't write to a string literal because they are read-only. And you shouldn't let a function named `print` write to a string literal for that matter. – Lundin Jan 24 '22 at 09:39
  • well, now inside the print function, i am only iterating over the passed string...I am not trying to write over it – avgkid Jan 24 '22 at 09:43
  • @AkTunes Well... how do you launch all of this? Is it a Linux-like bootloader on x86 or what? If so, how is the stack set up? What C run-time are you using if any? It might be that your code works until it has to use the stack, then goes haywire from there. – Lundin Jan 24 '22 at 09:47
  • i had actually took someone's help in making the bootloader. It was a custom bootloader not GRUB. i am not too comfortable with assembly, so i am not sure as to how the stack is set up, but I know that it is. I have included the stack part in the orignal question now. – avgkid Jan 24 '22 at 09:52
0
    char * sus = '\0';

Have not checked more... but this assigns a null pointer to sus, and most probably is not what you want to do.

Luis Colorado
  • 10,974
  • 1
  • 16
  • 31
0

Before proceeding I would recommend reading the OSDev wiki's page on text-based UIs.

While this may go beyond the scope of the question somewhat, I would strongly recommend that, rather than working with the character/attribute values as unsigned char manually, you might want to declare a struct type for those pairs:

struct TextCell {
    volatile unsigned char ch;
    volatile uint8_t attribute;
};

(You could actually be even more refined about it, by using a bitfield for the individual foreground, background, and decoration components of the attributes, but that's probably getting ahead of things.)

From there you can define the text buffer as a constant pointer:

const struct TextCell* text_buffer = (TextCell *)0xB8000;

You could further define

const uint16_t MAXH = 80, MAXV = 25;
uint16_t currv = 0, currh = 0;
struct TextCell* text_cursor = text_buffer;


void advance_cursor() {
    text_cursor++;
    if (currh < MAXH) {
        currh++;
    }
    else {
        currh = 0;
        if (currv < MAXV) {
            currv++;
        }
        else {
            /* handle scrolling */
        }
    }
}

void gotoxy(uint16_t x, uint16_t y) {
    uint16_t new_pos = x * y;
    if (new_pos > (MAXV * MAXH)) {
        text_cursor = text_buffer + (MAXV * MAXH);
        currh = MAXH;
        currv = MAXV;
    }
    else {
        text_cursor += new_pos;
        currh = x;
        currv = y;
}

Which would lead to the following modifications of your code:

void kprintc(char c, uint8_t attrib) {
    text_cursor->ch = c;
    text_cursor->attribute = attrib;
    advance_cursor();
}

void kprint(const char *string, uint8_t attribs) {
    int i;
    for (i = 0; string[i] != '\0'; i++) {
        kprintc(string[i], attribs);
    }
}

void clear_screen() {
    for(int i = 0; i < (MAXH * MAXV); i++) {
        kprintc(' ', 0);
    }
}

int bootup(void) {
    clear_screen();
    // kprintc('h', 0x03);
    // kprintc('e', 0x03);
    // kprintc('l', 0x03);
    // kprintc('l', 0x03);
    // kprintc('o', 0x03);
    kprint("hello", 0x03);
    return 1;
}

So, why am I suggesting all of this extra stuff? Because it is a lot easier to debug this way, mainly - it divides the concerns up better, and structures the data (or in this case, the video text buffer) more effectively. Also, you'll eventually need to do something like this at some point in the project, so if it helps now, you might as well do it now.

If I am out of line in this, please let me know.

Schol-R-LEA
  • 436
  • 4
  • 9