Page 1 of 1

The Bare Bones Tutorial In MonoDevelop

Posted: Mon Feb 09, 2015 7:07 pm
by SoulofDeity
This is a little trick I came up with. Note, this only works on Linux because the C/C++ language bindings don't work on Windows.


*) Make a GCC cross compiler and install MonoDevelop and xorriso (for grub-mkrescue)

*) Create a new empty C project for your kernel.

*) Open the project options

*) Go to the Build->Configurations tab, delete "Release", and rename "Debug" to "i686-elf"

*) Go to the Build->Output, change the output name to "myos.bin", and replace "Debug" in the output path with "i686-elf"

*) Go to the Build->Code Generation->Code Generation tab, change the warning level to "all", change the optimization level to "2", and remove "DEBUG" from the define symbols.

*) Under "Extra Compiler Options", add the following:

Code: Select all

-std=gnu99 -ffreestanding -Wextra
*) Under "Extra Linker Options", add the following:

Code: Select all

-T "/path/to/your/Project/Scripts/linker.ld" -ffreestanding -nostdlib
*) Go to the Build->Code Generation->Libraries tab and add "gcc".

*) Go to the Build->Custom Commands tab and add a new "After Build" command with the command set to:

Code: Select all

bash "Scripts/mkdisk.sh"
and the working directory set to "${ProjectDir}" (without quotes)

*) Add a new "After Clean" command with the command set to:

Code: Select all

rm -rf "../myos.iso" "../ISO"
and the working directory set to "${ProjectDir}" (without quotes)

*) Go to the Run->Custom Commands tab and add a new "Execute" command with the command set to:

Code: Select all

qemu-system-i386 -cdrom "${ProjectDir}/../myos.iso"
*) Click "OK" to save the changes to the project options.

*) Create a new directory called "Scripts".

*) Create a new file inside the "Scripts" directory called "mkdisk.sh" and add the following code to it:

Code: Select all

#!/bin/sh
mkdir -p ../ISO ../ISO/boot ../ISO/boot/grub
cp ./bin/i686-elf/myos.bin ../ISO/boot/myos.bin
cp ./Scripts/grub.cfg ../ISO/boot/grub/grub.cfg
grub-mkrescue -o ../myos.iso ../ISO
*) Create a new file inside the "Scripts" directory called "grub.cfg" and add the following content:

Code: Select all

menuentry "myos" {
  multiboot /boot/myos.bin
}
*) Create a new file inside the "Scripts" directory called "linker.ld" and add the following content:

Code: Select all

/* The bootloader will look at this image and start execution at the symbol
   designated as the entry point. */
ENTRY(_start)

/* Tell where the various sections of the object files will be put in the final
   kernel image. */
SECTIONS
{
	/* Begin putting sections at 1 MiB, a conventional place for kernels to be
	   loaded at by the bootloader. */
	. = 1M;

	/* First put the multiboot header, as it is required to be put very early
	   early in the image or the bootloader won't recognize the file format.
	   Next we'll put the .text section. */
	.text BLOCK(4K) : ALIGN(4K)
	{
		*(.multiboot)
		*(.text)
	}

	/* Read-only data. */
	.rodata BLOCK(4K) : ALIGN(4K)
	{
		*(.rodata)
	}

	/* Read-write data (initialized) */
	.data BLOCK(4K) : ALIGN(4K)
	{
		*(.data)
	}

	/* Read-write data (uninitialized) and stack */
	.bss BLOCK(4K) : ALIGN(4K)
	{
		*(COMMON)
		*(.bss)
		*(.bootstrap_stack)
	}

	/* The compiler may produce other sections, by default it will put them in
	   a segment with the same name. Simply add stuff here as needed. */
}
*) Create a new file under the main directory of the project called "boot.s" and add the following content:

Code: Select all

# Declare constants used for creating a multiboot header.
.set ALIGN,    1<<0             # align loaded modules on page boundaries
.set MEMINFO,  1<<1             # provide memory map
.set FLAGS,    ALIGN | MEMINFO  # this is the Multiboot 'flag' field
.set MAGIC,    0x1BADB002       # 'magic number' lets bootloader find the header
.set CHECKSUM, -(MAGIC + FLAGS) # checksum of above, to prove we are multiboot

# Declare a header as in the Multiboot Standard. We put this into a special
# section so we can force the header to be in the start of the final program.
# You don't need to understand all these details as it is just magic values that
# is documented in the multiboot standard. The bootloader will search for this
# magic sequence and recognize us as a multiboot kernel.
.section .multiboot
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM

# Currently the stack pointer register (esp) points at anything and using it may
# cause massive harm. Instead, we'll provide our own stack. We will allocate
# room for a small temporary stack by creating a symbol at the bottom of it,
# then allocating 16384 bytes for it, and finally creating a symbol at the top.
.section .bootstrap_stack, "aw", @nobits
stack_bottom:
.skip 16384 # 16 KiB
stack_top:

# The linker script specifies _start as the entry point to the kernel and the
# bootloader will jump to this position once the kernel has been loaded. It
# doesn't make sense to return from this function as the bootloader is gone.
.section .text
.global _start
.type _start, @function
_start:
	# Welcome to kernel mode! We now have sufficient code for the bootloader to
	# load and run our operating system. It doesn't do anything interesting yet.
	# Perhaps we would like to call printf("Hello, World\n"). You should now
	# realize one of the profound truths about kernel mode: There is nothing
	# there unless you provide it yourself. There is no printf function. There
	# is no <stdio.h> header. If you want a function, you will have to code it
	# yourself. And that is one of the best things about kernel development:
	# you get to make the entire system yourself. You have absolute and complete
	# power over the machine, there are no security restrictions, no safe
	# guards, no debugging mechanisms, there is nothing but what you build.

	# By now, you are perhaps tired of assembly language. You realize some
	# things simply cannot be done in C, such as making the multiboot header in
	# the right section and setting up the stack. However, you would like to
	# write the operating system in a higher level language, such as C or C++.
	# To that end, the next task is preparing the processor for execution of
	# such code. C doesn't expect much at this point and we only need to set up
	# a stack. Note that the processor is not fully initialized yet and stuff
	# such as floating point instructions are not available yet.

	# To set up a stack, we simply set the esp register to point to the top of
	# our stack (as it grows downwards).
	movl $stack_top, %esp

	# We are now ready to actually execute C code. We cannot embed that in an
	# assembly file, so we'll create a kernel.c file in a moment. In that file,
	# we'll create a C entry point called kernel_main and call it here.
	call kernel_main

	# In case the function returns, we'll want to put the computer into an
	# infinite loop. To do that, we use the clear interrupt ('cli') instruction
	# to disable interrupts, the halt instruction ('hlt') to stop the CPU until
	# the next interrupt arrives, and jumping to the halt instruction if it ever
	# continues execution, just to be safe. We will create a local label rather
	# than real symbol and jump to there endlessly.
	cli
	hlt
.Lhang:
	jmp .Lhang

# Set the size of the _start symbol to the current location '.' minus its start.
# This is useful when debugging or when you implement call tracing.
.size _start, . - _start
*) Right-click on "boot.s" and change the Build Action to "Compile".

*) Create a new file under the main directory of the project called "kernel.c" and add the following content:

Code: Select all

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>


enum vga_color
{
  COLOR_BLACK = 0,
  COLOR_BLUE = 1,
  COLOR_GREEN = 2,
  COLOR_CYAN = 3,
  COLOR_RED = 4,
  COLOR_MAGENTA = 5,
  COLOR_BROWN = 6,
  COLOR_LIGHT_GREY = 7,
  COLOR_DARK_GREY = 8,
  COLOR_LIGHT_BLUE = 9,
  COLOR_LIGHT_GREEN = 10,
  COLOR_LIGHT_CYAN = 11,
  COLOR_LIGHT_RED = 12,
  COLOR_LIGHT_MAGENTA = 13,
  COLOR_LIGHT_BROWN = 14,
  COLOR_WHITE = 15,
};


static const size_t VGA_WIDTH = 80;
static const size_t VGA_HEIGHT = 25;

size_t terminal_row;
size_t terminal_column;
uint8_t terminal_color;
uint16_t *terminal_buffer;


void terminal_initialize ();
void terminal_writestring (const char *data);


void
kernel_main ()
{
  terminal_initialize ();
  terminal_writestring ("Hello, kernel World!\n");
}

 
uint8_t
make_color (enum vga_color fg, enum vga_color bg)
{
  return fg | bg << 4;
}

uint16_t
make_vgaentry (char c, uint8_t color)
{
  uint16_t c16 = c;
  uint16_t color16 = color;
  return c16 | color16 << 8;
}

size_t
strlen (const char *str)
{
  size_t ret = 0;
  while (str[ret] != 0)
    ret++;
  return ret;
}
 
void
terminal_initialize ()
{
  terminal_row = 0;
  terminal_column = 0;
  terminal_color = make_color(COLOR_LIGHT_GREY, COLOR_BLACK);
  terminal_buffer = (uint16_t*) 0xB8000;
  for (size_t y = 0; y < VGA_HEIGHT; y++)
    {
      for (size_t x = 0; x < VGA_WIDTH; x++)
        {
          const size_t index = y * VGA_WIDTH + x;
          terminal_buffer[index] = make_vgaentry (' ', terminal_color);
        }
    }
}
 
void
terminal_setcolor (uint8_t color)
{
  terminal_color = color;
}
 
void
terminal_putentryat (char c, uint8_t color, size_t x, size_t y)
{
  const size_t index = y * VGA_WIDTH + x;
  terminal_buffer[index] = make_vgaentry (c, color);
}
 
void
terminal_putchar (char c)
{
  terminal_putentryat (c, terminal_color, terminal_column, terminal_row);
  if (++terminal_column == VGA_WIDTH)
    {
      terminal_column = 0;
      if (++terminal_row == VGA_HEIGHT)
        terminal_row = 0;
    }
}
 
void
terminal_writestring (const char *data)
{
  size_t datalen = strlen(data);
  for (size_t i = 0; i < datalen; i++)
    terminal_putchar (data[i]);
}
*) Close MonoDevelop

*) Create a new script on your desktop called "MonoDevelop.sh" and add the following content:

Code: Select all

#!/bin/sh
if [ `whoami` = "root" ]; then
  if [ "$1" = "setup" ]; then
    export TARGET=i686-elf
    export PREFIX=/path/to/your/crosscompiler
    export PATH="$PREFIX/bin:$PATH"
    if [ -f "/usr/bin/gcc.bak" ]; then
      rm -f "/usr/bin/gcc"
    else
      mv -f /usr/bin/gcc /usr/bin/gcc.bak
    fi
    ln -s "$PREFIX/bin/$TARGET-gcc" /usr/bin/gcc
  elif [ -f "/usr/bin/gcc.bak" ]; then
    mv -f /usr/bin/gcc.bak /usr/bin/gcc
  fi
else
  echo "Sudo sandwich is icky, but MonoDevelop must run in user mode."
  sudo -p "[sudo] password to setup /usr/bin/gcc: " $0 setup
  monodevelop
  sudo -p "[sudo] password to reset /usr/bin/gcc: " $0 reset
fi
*) Execute "chmod 777 ~/Desktop/MonoDevelop.sh" from the terminal to give the script execution priveleges. Clicking this script will be your new way of launching MonoDevelop. Note: On Ubuntu (at least for me) click-to-run shebanged bash scripts don't work in Unity, so you may need to run from the terminal or create a ".desktop" link to it.

*) Add water, shake, and bake at 375 degrees until golden brown. Serve hot out of the oven with an ice cold beverage.

------------------------------

And there you have it. The Bare Bones example works in MonoDevelop. You should be able to build, clean, and test your kernel in a really nice IDE now. In case anyone wants to know, the code for "kernel.c" is the same as the original Bare Bones code. It's just been refactored into GNU-style and reorganized a bit because I'm ocd.

Re: The Bare Bones Tutorial In MonoDevelop

Posted: Tue Feb 10, 2015 2:32 am
by Combuster
I think I missed the part where you tell it to actually use the cross-compiler.

Re: The Bare Bones Tutorial In MonoDevelop

Posted: Tue Feb 10, 2015 3:13 am
by SoulofDeity
Combuster wrote:I think I missed the part where you tell it to actually use the cross-compiler.
That's where the MonoDevelop.sh script comes in. The C/C++ languages bindings hard-coded the path to gcc to be at /usr/bin/gcc. There's no way to configure this. What the script does is make a sudo-sandwich. It creates a backup of the original gcc link if one doesn't exist and makes a link from /usr/bin/gcc to the cross compiler as a sudoer. Then it launches MonoDevelop as a normal user (which is required, otherwise all files created by the ide will require root priveleges to modify). Then it restores the backup of the original gcc as a sudoer after MonoDevelop has closed.

Re: The Bare Bones Tutorial In MonoDevelop

Posted: Tue Feb 10, 2015 4:19 am
by sortie
Chmod 777 is a terrible idea. It sounds like you were too lazy to actually learn Unix permissions. That means anyone can change the script that you expect to ask for sudo permissions? Huge security hole. I suggest you use chmod +x instead, as that honors the umask that's meant to protect you.

Also - GNU indention style? That is objectionally the worst possible way to indent C.
SoulofDeity wrote:
Combuster wrote:I think I missed the part where you tell it to actually use the cross-compiler.
That's where the MonoDevelop.sh script comes in. The C/C++ languages bindings hard-coded the path to gcc to be at /usr/bin/gcc. There's no way to configure this. What the script does is make a sudo-sandwich. It creates a backup of the original gcc link if one doesn't exist and makes a link from /usr/bin/gcc to the cross compiler as a sudoer. Then it launches MonoDevelop as a normal user (which is required, otherwise all files created by the ide will require root priveleges to modify). Then it restores the backup of the original gcc as a sudoer after MonoDevelop has closed.
This sounds like a terrible, terrible idea. Fix your IDE so the compiler path is configuration. Don't #@&!ng pull **** like that.

Re: The Bare Bones Tutorial In MonoDevelop

Posted: Tue Feb 10, 2015 4:34 am
by SoulofDeity
sortie wrote:Chmod 777 is a terrible idea. It sounds like you were too lazy to actually learn Unix permissions. That means anyone can change the script that you expect to ask for sudo permissions? Huge security hole. I suggest you use chmod +x instead, as that honors the umask that's meant to protect you.
I know UNIX permissions. I'm just not paranoid.
sortie wrote:Also - GNU indention style? That is objectionally the worst possible way to indent C.
That's subjective, but okay.
sortie wrote:This sounds like a terrible, terrible idea. Fix your IDE so the compiler path is configuration. Don't #@&!ng pull **** like that.
I didn't make the ide. Seriously, have you never heard of MonoDevelop? It's the basis of Xamarin Studio and the Sony PSM Developer Suite; also a used by the famous Unity3D editor. It's the top dog of ide's right up there with Visual Studio. Could I make a plugin or patch to do it? Maybe... But that takes time and effort. I'm lazy, and this was just a hack that I came up with while playing around which I thought that other people would find very useful.

Re: The Bare Bones Tutorial In MonoDevelop

Posted: Tue Feb 10, 2015 7:31 am
by sortie
Ok, I agree that if it works for you, it works for you.

My point was rather to port out hacks in your methods that I object to - and to avoid casual readers that follow your advise from falling into your traps. After all, you decided to share these instructions, so there is the implicit connotation that they should be usable for other people as well. I expect a number of people here have user accounts where a shell script as 777 is just damn dangerous. Perhaps they also don't have root access.

I suggest you file a bug request for the IDE so you can control which compiler is used. Hacks like this are just dangerous, especially since many distros do package update mechanisms of kernel drivers that require gcc invocations, you don't want those to use your cross-compiler by accident. File a bug report or add the minor feature yourself. :)

I've not heard of MonoDevelop. I quite simply don't use IDEs. I tend to find them overly complex and not flexible enough. I'm happy with make, a shell, and a simple decent text editor. This is also nice because that's easier to port to my OS, compared to a full IDE. But that's my personal style, I don't object to people using IDEs.

Re: The Bare Bones Tutorial In MonoDevelop

Posted: Tue Feb 10, 2015 9:53 am
by AndrewAPrice
sortie wrote:I've not heard of MonoDevelop. I quite simply don't use IDEs. I tend to find them overly complex and not flexible enough. I'm happy with make, a shell, and a simple decent text editor. This is also nice because that's easier to port to my OS, compared to a full IDE. But that's my personal style, I don't object to people using IDEs.
MonoDevelop is an open source .Net IDE, and it's tightly coupled with Mono's runtime and compilers. For that, it's great and it works seemlessly. SoulofDeity is trying to do something out of the norm. It's a decent IDE, so if he can share what he learns, others may benefit from it.