Reputation: 622
I'm trying to create a kernel from scratch (just trying something new)
Everything is ready now and I'm testing the output and noticed something very weird
I build my files with:
gcc ./kernel/kernel.c -ffreestanding -O0 -m32 -c -o./bin/kernel.o -fno-pie
and link them together with:
ld -nostdlib -nodefaultlibs -Tlink.ld ./bin/kernel_ep.elf.bin ./bin/kernel.o -o ./bin/kernel.bin
So from my understanding, I already tell the compiler NOT to optimize my code.
Now the C part
#define BYTE unsigned char
#define VIDMEM ((BYTE*)0xb8000)
void init();
void main() {
init();
while(1);
}
void print(char *msg)
{
volatile BYTE *screen = VIDMEM;
for(const char *msgPtr = msg; *msgPtr; ++msgPtr)
{
*(screen++) = *msgPtr;
screen++;
}
}
void init() {
//volatile char test[] = "Test";
//print(test);
print("Test");
}
If i run this, nothing will happen, I checked the whole thing in ghidra - the char array of "Test" is in the memory, but I have no reference to init() -> So print
never gets called.
If I now use the commented text (and comment print("Test")
) everything works fine, text gets printed the way I want.
But the real question is: Is there some kind of "Trick" to tell the compiler NOT to optimize this code except using volatile? because I don't think declearing everything which could maybe be printed as volatile is the way I should do this.
As far as I understand, the main problem is that the print
function basically does nothing, because the compiler doesn't seem to know that 0xb8000 is a kinda... special adress.
Upvotes: 1
Views: 119
Reputation: 15586
Your volatile
char array is on the stack, while your string literal is not. I suspect that you have incorrectly set up your data segment register. I'll go ahead and clone your repository and test that theory out.
Upvotes: 0
Reputation: 67721
This Microsoft style of defining the types is just horrible. Use exact size types like uint8_t
or int32_t
. Defining BYTE
is a very bad habit. It will no cause problems here but the problem starts with wider types and different sizes on different systems.
The compiler will inline both functions in this trivial example. So you will not see the init
or print
function calls. If you want to make it noninlineble use __attribute__((noinline))
.
you do not need volatile as the code will not be optimized.
#include <stdint.h>
#define VIDMEM ((uint8_t*)0xb8000)
void __attribute__((noinline)) print(const char *msg)
{
uint8_t *screen = VIDMEM;
for(const char *msgPtr = msg; *msgPtr; ++msgPtr)
{
*screen++ = *msgPtr;
}
}
void __attribute__((noinline)) init() {
print("Test");
}
void main()
{
init();
while(1);
}
Here you have the inlined version. Function are static to prevent compiler from having another copy for the external linkage.
Upvotes: 3
Reputation: 81217
While compilers used to treat integer-to-pointer conversions as indicating points where compilers shouldn't assume anything about the resulting pointer (essentially implying volatile semantics in the case where the result of each act of conversion was only used once) neither clang nor gcc provides any option to support such semantics except -O0
.
Upvotes: -1