Cannot access global and static variables

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
b52
Posts: 3
Joined: Fri Jan 02, 2015 10:10 am

Cannot access global and static variables

Post by b52 »

I'm (trying) to write a kernel. I've already written the bootloader, from scratch, and it properly loads and boots the kernel in protected mode.

Code: Select all

    mov bx, 0x1000
    mov dh, 2
    mov dl, [boot_drive]
    call load_disk ; loads [dh] sectors to [bx] from [boot_drive]
    ; enter protected mode
    ...
    ; jumps to 0x1000
    jmp 0x1000
The bootloader loads the kernel from the boot image to 0x1000, prints some messages, enter protected mode, then jumps to the kernel entry point _start. At this point, I can successfully write to VGA and set up the stack.

Code: Select all

section .text
global _start
_start:
    mov esp, _stack+8192
    extern main
    call main
    jmp $

section .bss
_stack:
resb 8192
The 'main' symbol is the C part of the kernel entry point, linked with ld with the assembled part.
The problem is: global and static variables do not work at all.

Code: Select all

int main() { *((char*)0xB8000) = 0x38; } // works, displays 8

Code: Select all

// global
char* vga = (char*)0xB8000; 
int main() { vga[0] = 0x38; } // does not work
Local stack variables do work correctly, that is

Code: Select all

int main() {
    char* video = (char*)0xb8000;
    char ch = 'P';
    video[0] = ch;
}
successfully prints 'P' (even with no compiler optimizations).
The same applies to static variables in any function.
My setup is:
gcc/tcc elf cross-compilers (this behavior does not depend on the compiler I use);
nasm assembler;
ld linker;
qemu.
Attached is the linker script.
linker.ld
(479 Bytes) Downloaded 105 times
At first I did not follow OSDev Wiki, but I read every related article when I got this issue and found no solution.
What could I do to solve this problem? I guess it comes from the linking stage; changing the values of declared global variables in C does not change their values at all. Writing their addresses to known locations and reading them from assembly doesn't work (prints random data or nothing), so probably their addresses are got wrong by the compiler. I'm sure I'm missing something.
Rew
Member
Member
Posts: 28
Joined: Mon Oct 29, 2012 2:26 pm

Re: Cannot access global and static variables

Post by Rew »

Did you try..

Code: Select all

// global
char* vga;
int main() 
{
vga = (char*)0xB8000 
vga[0] = 0x38; 
} 
It's not that static variables don't work, it is that static initialization doesn't happen. Typically people create functions to do static initialization, or sometimes (usually c++) people figure out how initializers are compiled by their compiler and call the initializers at the appropriate time themselves.
User avatar
KemyLand
Member
Member
Posts: 213
Joined: Mon Jun 16, 2014 5:33 pm
Location: Costa Rica

Re: Cannot access global and static variables

Post by KemyLand »

Rew wrote:Did you try..

Code: Select all

// global
char* vga;
int main() 
{
vga = (char*)0xB8000 
vga[0] = 0x38; 
} 
It's not that static variables don't work, it is that static initialization doesn't happen. Typically people create functions to do static initialization, or sometimes (usually c++) people figure out how initializers are compiled by their compiler and call the initializers at the appropriate time themselves.
That's not the problem. He's using C, not C++. Already initialized global variables are stored in .data. So it should contain the correct value (0xB8000). I do think even C++ does this...

I think his problem is that he's using char* instead of uint16_t*. Text VGA memory is divided is 16-bit blocks, so color information can also be stored. If you write to the first byte, you're essentially writting 'P' to a attribute byte. Use the examples in the Bare Bones tutorial. In short, you'll do this to write to VGA:

Code: Select all

// Don't worry! GCC provides these headers even on freestanding environments!
#include <stddef.h>
#include <stdint.h>

#define VGA_WIDTH 25
#define VGA_HEIGHT 80

typedef enum {
  VGAColorBlack = 0,
  VGAColorBlue = 1,
  VGAColorGreen = 2,
  VGAColorCyan = 3,
  VGAColorRed = 4,
  VGAColorMagenta = 5,
  VGAColorBrown = 6,
  VGAColorLightGrey = 7,
  VGAColorDarkGrey = 8,
  VGAColorLightBlue = 9,
  VGAColorLightGreen = 10,
  VGAColorLightCyan = 11,
  VGAColorLightRed = 12,
  VGAColorLightMagenta = 13,
  VGAColorLightBrown = 14,
  VGAColorWhite = 15,
} VGAColor;

uint8_t VGAMakeColor(VGAColor back, VGAColor fore) {
  return (((uint8_t)back) << 4) | (uint8_t)fore;
}

void VGAWriteChar(char c, VGAColor back, VGAColor fore) {
  static unsigned x = 0;
  static unsigned y = 0;
  static uint16_t* const vga = (uint16_t*)0xB8000;  
  
  unsigned pos = y * VGA_WIDTH + x;
  vga[pos] = ((uint16_t)VGAMakeColor(back, fore) << 8) | c;
  
  if(++x == VGA_WIDTH) {
    x = 0;
    if(++y == VGA_HEIGHT)
      y = 0;
  }
}

void VGAWrite(const char *str) {
  size_t size = strlen(str);
  
  for(size_t i = 0; i < size; i++)
    VGAWriteChar(str[i], VGAColorBlack, VGAColorWhite); // You can play with the VGA colors!
}

int main() {
  VGAWrite("Hello, Kernel World!");
  return 0;
}
BTW, b52, are you using QEMU? If so, you can use GDB to debug your problems. Do this:

Code: Select all

qemu-system-i386 ... -s -S
Now, open GDB

Code: Select all

$ gdb
...
file <myos>
target remote localhost:1234
Now you can debug your OS with GDB! Just place a breakpoint at main and do it the usual way:

Code: Select all

break main
continue
...
Tell us if you're using another emulator, so we can help :wink: .
Happy New Code!
Hello World in Brainfuck :D:

Code: Select all

++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
[/size]
b52
Posts: 3
Joined: Fri Jan 02, 2015 10:10 am

Re: Cannot access global and static variables

Post by b52 »

Actually, my printing function takes two arguments 1 byte each, one for the character and another for the color (4 bits for background and 4 for foreground). I default it to 0x0F for my printf implementation.
I can print correct values on screen and do everything else on screen. As long as I do not use globals or statics.
I can't keep track of the cursor (the last printed location) so I have to carry around a variable to pass to every function.
Having a global variable would be very useful. My standard library implementation works well but every printing function now takes one more argument for the cursor, and that's not very nice. And that's only the beginning.
As the kernel develops, I don't want to keep a huge god structure that contains everything to pass to any function just because I can't use global variables.
I also found that some operators return wrong results, such as -1 for v % 2, where v is a local variable of value 6, and other weird things.
Manually printing the address of any global variable results in... 0xFFFFFFFF, and that's nonsense.
I'll rewrite everything from scratch following the osdev wiki, but I already discarded lot of my code and nothing changed.

However, knowing that I can use gdb is awesome! I'll try as soon as I get to my machine.
User avatar
KemyLand
Member
Member
Posts: 213
Joined: Mon Jun 16, 2014 5:33 pm
Location: Costa Rica

Re: Cannot access global and static variables

Post by KemyLand »

Mmm, this is really strange. Can you pass me your kernel's sources by PM? If I find anything, I'll post on here for completeness.
Happy New Code!
Hello World in Brainfuck :D:

Code: Select all

++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
[/size]
User avatar
eryjus
Member
Member
Posts: 286
Joined: Fri Oct 21, 2011 9:47 pm
Libera.chat IRC: eryjus
Location: Tustin, CA USA

Re: Cannot access global and static variables

Post by eryjus »

I'm going to suggest you use readelf to investigate all of your object files before linking and make sure you have all your sections accounted for in your linker command file.
Adam

The name is fitting: Century Hobby OS -- At this rate, it's gonna take me that long!
Read about my mistakes and missteps with this iteration: Journal

"Sometimes things just don't make sense until you figure them out." -- Phil Stahlheber
b52
Posts: 3
Joined: Fri Jan 02, 2015 10:10 am

Re: Cannot access global and static variables

Post by b52 »

SOLVED!

As suggested by eryjus, I inspected the kernel object file with readelf and I noticed that global variables were not at the proper address. For GDB any global or static was at 0x0. For some reason, the assembler outputted a elf64 file (maybe a misconfiguration or 64bit is the nasm default for elf), so every subsequent linking stage had the wrong addresses. Well, that should have made impossible to run the kernel at all, especially on qemu i386, but everything worked except global variables. Don't know why.
Now I initialize globals in the main function and everything is ok.
For the modulus operator error, tcc behaves better than gcc so I'll continue using it.

Thank you all for your help, you're awesome.
FallenAvatar
Member
Member
Posts: 283
Joined: Mon Jan 03, 2011 6:58 pm

Re: Cannot access global and static variables

Post by FallenAvatar »

b52 wrote:SOLVED! ...
Glad to hear you solved it. Put I feel the need to point out line one in your attached linker.ld. Something I saw right when you posted (but was not in the mindset to be able to reply) as I thought anyone else would see it and point it out.
OUTPUT_FORMAT("binary")
"binary" != "elf32-i386" && "binary" != "elf64-x86-64"

Please see http://wiki.osdev.org/Linker_Scripts#OUTPUT_FORMAT (And the rest of that page as well) and correct your linker script immediately, or you will see other strange errors.

- Monk

P.S. Given you wrote your own bootloader, you will need to use binary as your output format, but this means you MUST understand the repercussions of doing so.
Octocontrabass
Member
Member
Posts: 5590
Joined: Mon Mar 25, 2013 7:01 pm

Re: Cannot access global and static variables

Post by Octocontrabass »

b52 wrote:For the modulus operator error, tcc behaves better than gcc so I'll continue using it.
GCC does not have errors (that you will ever come across). You're probably still doing something wrong, though I'd need a bit more information to tell you what.
Post Reply