3

I am currently programming my own operating system (just for fun, I am 16) and have a problem with the outprint function i have crated. I want to change the text color (not the background color) but it won't work.

I have crated my own printf function SystemOutPrint/ln (named after Java cause thats my "main" language, for those who wounder) and figured out that I can change the background color by writing in the AH register. Before this function, i did nothing in the Kernel at all and the bootloader only sets the GDT, LDT and the mode from 16 to 32 bits. So the video memory is untouched until the mov ebx, 0xb8000.

The relevant code:

kmain:
mov ebx, 0xb8000    ;Video Memory
mov esi, kernelVersion
mov ah, 0x0
;setting ah to 0x0 is not neccessary, because its the default but if you
;would put in A for example it would be light green, etc.
call SystemOutPrintln

SystemOutPrintln:
mov ecx, ebx

.printChar:
lodsb
test al,al
jz .newLine
or eax,0x0F00
mov word [ebx], ax
add ebx, 2
jmp .printChar

.newLine:
mov edx, ebx
sub edx, ecx
mov ecx, 0x000A0
sub ecx, edx
add ebx, ecx
ret

kernelVersion: db "Kernel Version: 0.0.1", 0

What I tried: Changing every byte in eax to find find the attribute byte for the foregound color. By doing this try and error like, i found out, that changing ah works for the background color, but al is used in test al, al to find the end of the string and the part of eax that is not ax is simply dismissed by the function. I didn't try changing something in the other registers, because they are used for something else or for nothing at all, so it does not make sence to me. A website (that i didnt find to link it) said the attribute byte is defined like that: Hex Value of BG Color (say F for white) * 16 = F0 + Hex Value of FG Color (Lets take the A for light green) what should be FA. If i do "mov ah, 0xFA" i change the background to white, but the foreground still is white (default).

Providing an minimal repoducible example for this would not be minimal anymore, because i would have to give you the bootloader and GDT aswell. But it would be enough of an answer if someone could tell me which byte is the foreground color attribute byte, so i can focus on trying to get that byte to work instead of having to rewrite the whole video momory in try and error.

RAVN Mateus
  • 560
  • 3
  • 13
  • And by the way: Why does the Website mess up my code formatting so hard? In the Edit it looks how it is supposed to look, here it's a complete mess. – RAVN Mateus Aug 08 '19 at 12:03
  • Stack Overflow uses a variant of Markdown. Refer to the [editing help](https://stackoverflow.com/editing-help) for details. – fuz Aug 08 '19 at 12:12
  • What matters is the bytes you store to VGA memory, when you're in VGA text mode. Since you're storing AX to memory, the attribute byte (higher address of the pair) comes from AH. But IDK why you're using inefficient `or al,al` instead of `test al,al`, or why you're using `or eax, 0x0f00` inside the loop instead of just setting AH once outside the loop. `lodsb` already merges a new AL into the existing EAX. – Peter Cordes Aug 08 '19 at 12:13
  • How did you *try* to change the FG color? This isn't a [mcve] because you don't show anything about what you tried that didn't work. (Or where you're storing these bytes, or anything you did to the VGA mode before this). – Peter Cordes Aug 08 '19 at 12:15

2 Answers2

5

First; let's optimise your code a little. Specifically, for this loop:

.printChar:
    lodsb
    or al,al
    jz .newLine
    or eax,0x0F00
    mov word [ebx], ax
    add ebx, 2
    jmp .printChar

..after the first iteration the value in ah won't change; so that instruction can be lifted out of the loop to improve performance. Also or eax,0x0F000 has the same effect as a shorter or ah,0x0F. With both those changes it ends up like this:

    or ah,0x0F
.printChar:
    lodsb
    or al,al
    jz .newLine
    mov word [ebx], ax
    add ebx, 2
    jmp .printChar

Now add some comments, like this:

    or ah,0x0F          ;Force the foreground colour for all characters to be white
.printChar:
    lodsb               ;al = next character
    or al,al            ;Is the next character zero?
    jz .newLine         ; yes, don't print it and move to the next line instead
    mov word [ebx], ax  ;Store next character (from string) and attribute (from outside the loop)
    add ebx, 2          ;bx = address to store next character and attribute
    jmp .printChar

Notice that comments (e.g. "Force the foreground colour for all characters to be white") are useful because:

  • they reduce the time it takes to understand what the code is supposed to be doing

  • they make it easy to see bugs where the instruction doesn't do what the comment says it is supposed to do

Brendan
  • 35,656
  • 2
  • 39
  • 66
  • Thanks for the answer, i figured it out myself aswell when i read the comment Peter Cordes and thought "wait why did i put that here?". And yeah i am gonna use more comments, i started to work on the os again after a few weeks and didnt see i made the fg white for everything. – RAVN Mateus Aug 08 '19 at 13:20
  • 1
    `mov word [ebx], ax` can be written as `mov [ebx], ax` . The loop could be optimized by having the conditional branch that checks for NUL(0) at the bottom of the loop. That would require one unconditional branch before the loop that jumps inside the loop where the character is checked. As well had the OP used _EDI_ for the video address then the `mov [ebx], ax` `add ebx, 2` could have been written as `stosw`. As peter pointed out `or al, al` is better done with `test al, al` – Michael Petch Aug 08 '19 at 13:28
  • 1
    If you're going to talk about optimizing this, you can make it run faster on CPU that don't rename AL separately from EAX. (Haswell and later, and all non-Intel). Replace `or al,al` with `test al,al` so the loop-carried dep chain is just repeated merging of a new low byte of EAX. And so `test/jz` can macro-fuse. [Test whether a register is zero with CMP reg,0 vs OR reg,reg?](//stackoverflow.com/a/33724806) has more details. – Peter Cordes Aug 08 '19 at 20:59
  • 1
    @MichaelPetch: `stosw` is not a speed optimization on modern CPUs, only code-size. STOS is 3 uops on AMD/Intel CPUs, vs. 2 fused-domain uops for `mov` + `add` (https://agner.org/optimize/). Also `lodsb/w` is 3 uops vs. 2 for `lodsd/q` on Haswell and later. But yes, skewing the loop to put the conditional branch at the bottom would help; on most CPUs this bottlenecks on front-end bandwidth. Peeling part of the first iteration would be an alternative to `jmp` into the middle of a loop. e.g. with `movzx eax, byte [esi]` / `or eax, 0x0f00` / fall into a loop that starts with the store. – Peter Cordes Aug 08 '19 at 21:04
  • Also or eax,0x0F000 has the same... —— 0x0F000 should be 0x0F00 – John Xiao Jul 20 '22 at 09:37
1

The reason I couldn't change the FG Color was the or eax, 0x0f00.

The f is the nibble which is supposed to define the foreground color, but because of the or, everything I put in there before is overridden. Thanks to Peter Cordes for asking why I am "using or eax, 0x0f00 inside the loop instead of just setting AH once outside the loop." Due to that I tried to leave the or out and it worked just how it was supposed to, just not ignoring the FG I set before anymore.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
RAVN Mateus
  • 560
  • 3
  • 13
  • Your question was really unclear. I assumed you meant you were changing the `0x0f00` to some other number in the OR instruction. The comment on `mov ah, 0` made no sense because AH doesn't have a "default" value; it has whatever garbage is left there. Anyway, that's why a MCVE needs to show what exactly you tried that didn't work. Now you know for future questions at least. – Peter Cordes Aug 08 '19 at 20:54