Click here to Skip to main content
Click here to Skip to main content

C# Open Source Managed Operating System - Intro to Plugs

By , 4 Jul 2011
 

Introduction

This article will demonstrate how to implement .NET code that relies on Windows APIs or internal calls in Cosmos. In addition, it will cover how to interface directly to the hardware using Cosmos and assembly or X#.

What is Cosmos?

Cosmos is an Operating System development kit (more info) which uses Visual Studio as its development environment. Despite C# in the name, any .NET based language can be used including VB.NET, Fortran, Delphi Prism, IronPython, F#, and more. Cosmos itself and the kernel routines are primarily written in C#, and thus the Cosmos name. Besides that, NOSMOS (.NET Open Source Managed Operating System) sounds stupid.

Cosmos is not an Operating System in the traditional sense, but instead it is an "Operating System Kit", or as I like to say "Operating System Legos". Cosmos lets you create Operating Systems just as Visual Studio and C# normally let you create applications. Most users can write and boot their own Operating System in just a few minutes, all using Visual Studio. Cosmos supports integrated project types in Visual Studio, and an integrated debugger, breakpoints, watches, and more. You can debug your Operating System the same way that you debug a normal C# or VB.NET application.

The Need For Plugs

Plugs are needed in three scenarios in Cosmos:

  1. Internal Calls
  2. PInvoke
  3. Direct Assembly

Internal Calls and PInvoke

Some methods in .NET classes are implemented without using .NET code. They are implemented using native code. There are two reasons for this:

  1. The method relies on the Windows API (PInvoke).
  2. The method relies on highly optimized C++ or assembly (internal calls) which exist in the .NET runtime.

PInvoke is used to draw to the screen, access existing Windows encryption APIs, access networking, and other similar functions.

Internal Calls (icalls) are used for classes which require direct access to the .NET runtime. Examples are classes which need to access memory management, or in some cases for raw speed. The Math.Pow method uses an internal call.

Plugs can be written in C# (or any .NET language), or assembly.

Direct Assembly

To talk to hardware, Cosmos must be able to interact with the PCI bus, CPU IO Bus, memory, and more. Memory can usually be accessed using unsafe pointers; however, in other cases, the assembly code must be handwritten. Plugs can be used to interface C# to assembly code directly, making the assembly calls callable as if normal C# code is being called.

Writing X86 Assembly in Cosmos

An X86 assembly can be written in Cosmos using classes:

new Move(Registers.DX, (xComAddr + 1).ToString());
new Move(Registers.AL, 0.ToString());
new Out("dx", "al");// disable all interrupts
new Move(Registers.DX, (xComAddr + 3).ToString());
new Move(Registers.AL, 0x80.ToString());
new Out("dx", "al");//  Enable DLAB (set baud rate divisor)
new Move(Registers.DX, (xComAddr + 0).ToString());
new Move(Registers.AL, 0x1.ToString());
new Out("dx", "al");//      Set diviso (low byte)
new Move(Registers.DX, (xComAddr + 1).ToString());
new Move(Registers.AL, 0x00.ToString());
new Out("dx", "al");// // set divisor (high byte)

But Cosmos also supports a higher level abstraction called X#. X# is a typesafe assembly language which maps to X86 assembly. X# looks like this:

UInt16 xComStatusAddr = (UInt16)(aComAddr + 5); 
Label = "WriteByteToComPort";
Label = "WriteByteToComPort_Wait";
 
DX = xComStatusAddr;
AL = Port[DX];
AL.Test(0x20);
JumpIfEqual("WriteByteToComPort_Wait");
DX = aComAddr;
AL = Memory[ESP + 4];
Port[DX] = AL;
Return(4);
Label = "DebugWriteEIP";
AL = Memory[EBP + 3];
EAX.Push();
Call<WriteByteToComPort>();
AL = Memory[EBP + 2];
EAX.Push();
Call<WriteByteToComPort>();
AL = Memory[EBP + 1];
EAX.Push();
Call<WriteByteToComPort>();
AL = Memory[EBP];
EAX.Push();
Call<WriteByteToComPort>();
Return();

Writing Plugs

To write a plug, first we must decide the target of our plug. For example, Math.Abs(double) is implemented as an internal call.

.method public hidebysig static float64 Abs(float64 'value') cil managed internalcall
{
    .custom instance void System.Security.SecuritySafeCriticalAttribute::.ctor()
}

If you try to call it in Cosmos without a plug, the compiler will produce an error, "plug needed", because there is no IL code in it for IL2CPU to compile X86. So the "plug needed" error means that you have used some method which relies on an internal call or PInvoke, and thus Cosmos cannot compile it.

In the case of Math.Pow, it will work because the Cosmos kernel already includes a plug which is automatically used.

The plug is used by the compiler at runtime to substitute code. Instead of performing an internal call or Windows API which is impossible for Cosmos because it is not running under the CLR or Windows, the replacement code supplied by the plug is inserted and used. It is a form of forced inlining and replacement.

To create the plug, we need to create a new class. Plugs in the kernel are kept in separate assemblies and referenced by the kernel. This allows IL2CPU to include and use the plugs.

[Plug(Target = typeof(global::System.Math))]
public class MathImpl  {
        public static double Abs(double value)  {
            if (value < 0) {
                return -value;
            } else {
                return value;
            }
        }

Plug classes can contain more than one method, although this excerpt only shows one. The Plug attribute is the key item in this example. It tells IL2CPU that this class is used to replace methods of the System.Math class. It then proceeds to find methods to match against the methods in System.Math.

Direct Assembly Plugs

Direct assembly plugs are used to allow C# to interface directly to X86 assembly code. An example of this is the IOPort class which allows device drivers to directly access the CPU bus which is required to talk to many hardware devices.

First, an empty class written partially in C# is created. Methods that will be plugged by the assembly are created empty. However, if they are not of return type void, a dummy value must be returned so that the C# compiler will compile it. The return value however will not be used, because plugs cause the target method's implementation to be ignored and the plug's implementation to be used instead.

public abstract class IOPortBase  {
    public readonly UInt16 Port;
 
    // all ctors are internal - Only Core ring
    // can create it.. but hardware ring can use it.
    internal IOPortBase(UInt16 aPort)
    {
        Port = aPort;
    }
    internal IOPortBase(UInt16 aBase, UInt16 aOffset)
    {
        // C# math promotes things to integers, so we have this constructor
        // to relieve the use from having to do so many casts
        Port = (UInt16)(aBase + aOffset);
    }
 
    //TODO: Reads and writes can use this to get port instead of argument
    static protected void Write8(UInt16 aPort, byte aData) { } // Plugged
    static protected void Write16(UInt16 aPort, UInt16 aData) { } // Plugged
    static protected void Write32(UInt16 aPort, UInt32 aData) { } // Plugged
 
    static protected byte Read8(UInt16 aPort) { return 0; } // Plugged
    static protected UInt16 Read16(UInt16 aPort) { return 0; } // Plugged
    static protected UInt32 Read32(UInt16 aPort) { return 0; } // Plugged

As you can see, the Write methods are empty, but the Read methods require a dummy value.

The class is then plugged using this code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Cosmos.IL2CPU.Plugs;
using Assembler = Cosmos.Compiler.Assembler.Assembler;
using CPUx86 = Cosmos.Compiler.Assembler.X86;

namespace Cosmos.Core.Plugs
{
    [Plug(Target = typeof(Cosmos.Core.IOPortBase))]
    public class IOPortImpl
    {
        [Inline]
        public static void Write8(UInt16 aPort, byte aData)
        {
            //TODO: This is a lot of work to write to a single port.
            // We need to have some kind of inline ASM option that can
            // emit a single out instruction
            new CPUx86.Move { DestinationReg = CPUx86.Registers.EDX, 
                SourceReg = CPUx86.Registers.EBP, SourceDisplacement = 0x0C, 
                SourceIsIndirect = true };
            new CPUx86.Move { DestinationReg = CPUx86.Registers.EAX, 
                SourceReg = CPUx86.Registers.EBP, SourceDisplacement = 0x08, 
                SourceIsIndirect = true };
            new CPUx86.Out { DestinationReg = CPUx86.Registers.AL };
        }
 
        [Inline]
        public static void Write16(UInt16 aPort, UInt16 aData)
        {
            new CPUx86.Move { DestinationReg = CPUx86.Registers.EDX, 
                SourceReg = CPUx86.Registers.EBP, SourceIsIndirect = true, 
                SourceDisplacement = 0x0C };
            new CPUx86.Move { DestinationReg = CPUx86.Registers.EAX, 
                SourceReg = CPUx86.Registers.EBP, SourceIsIndirect = true, 
                SourceDisplacement = 0x08 };
            new CPUx86.Out { DestinationReg = CPUx86.Registers.AX };
        }
 
        [Inline]
        public static void Write32(UInt16 aPort, UInt32 aData) 
        {
            new CPUx86.Move { DestinationReg = CPUx86.Registers.EDX, 
                SourceReg = CPUx86.Registers.EBP, SourceIsIndirect = true, 
                SourceDisplacement = 0x0C };
            new CPUx86.Move { DestinationReg = CPUx86.Registers.EAX, 
                SourceReg = CPUx86.Registers.EBP, SourceIsIndirect = true, 
                SourceDisplacement = 0x08 };
            new CPUx86.Out { DestinationReg = CPUx86.Registers.EAX };
        }
 
        [Inline]
        public static byte Read8(UInt16 aPort)
        {
            new CPUx86.Move { DestinationReg = CPUx86.Registers.EDX, 
                SourceReg = CPUx86.Registers.EBP, SourceIsIndirect = true, 
                SourceDisplacement = 0x08 };
            //TODO: Do we need to clear rest of EAX first?
            //    MTW: technically not, as in other places,
            //         it _should_ be working with AL too..
            new CPUx86.Move { DestinationReg = CPUx86.Registers.EAX, SourceValue = 0 };
            new CPUx86.In { DestinationReg = CPUx86.Registers.AL };
            new CPUx86.Push { DestinationReg = CPUx86.Registers.EAX };
            return 0;
        }
 
        [Inline]
        public static UInt16 Read16(UInt16 aPort)
        {
            new CPUx86.Move { DestinationReg = CPUx86.Registers.EDX, 
                SourceReg = CPUx86.Registers.EBP, 
                SourceIsIndirect = true, SourceDisplacement = 0x08 };
            new CPUx86.Move { DestinationReg = CPUx86.Registers.EAX, SourceValue = 0 };
            new CPUx86.In { DestinationReg = CPUx86.Registers.AX };
            new CPUx86.Push { DestinationReg = CPUx86.Registers.EAX };
            return 0;
        }

        [Inline]
        public static UInt32 Read32(UInt16 aPort)
        {
            new CPUx86.Move { DestinationReg = CPUx86.Registers.EDX, 
                SourceReg = CPUx86.Registers.EBP, 
                SourceIsIndirect = true, SourceDisplacement = 0x08 };
            new CPUx86.In { DestinationReg = CPUx86.Registers.EAX };
            new CPUx86.Push { DestinationReg = CPUx86.Registers.EAX };
            return 0;
        }
    }
}

Note that the code in this case is not written in X#. Many of our older plugs are still written in the older syntax.

Now that we have a plug, we can access the IOPort class directly in C# code. This example is from the ATA (Hard disk) class.

public override void ReadBlock(UInt64 aBlockNo, UInt32 aBlockCount, byte[] aData) {
      CheckDataSize(aData, aBlockCount);
      SelectSector(aBlockNo, aBlockCount);
      SendCmd(Cmd.ReadPio);
      IO.Data.Read8(aData);
}

Another Plug Example

In the BCL, the Console class calls several internal methods which finally end up in Windows API calls. We don't need to plug only the methods that directly map to Windows API calls, but instead we plug methods much higher up the tree and completely replace the implementation to call our TextScreen class instead.

namespace Cosmos.System.Plugs.System.System {
[Plug(Target = typeof(global::System.Console))]
public static class ConsoleImpl {

    private static ConsoleColor mForeground = ConsoleColor.White;
    private static ConsoleColor mBackground = ConsoleColor.Black;

    public static ConsoleColor get_BackgroundColor() {
        return mBackground;
    }

    public static void set_BackgroundColor(ConsoleColor value) {
        mBackground = value;
        Cosmos.Hardware.Global.TextScreen.SetColors(mForeground, mBackground);
    }

License

This article, along with any associated source code and files, is licensed under The BSD License

About the Author

Chad Z. Hower aka Kudzu
Other
Cyprus Cyprus
Member
Chad Z. Hower, a.k.a. Kudzu
"Programming is an art form that fights back"
www.KudzuWorld.com
 
Formerly the Regional Developer Adviser (DPE) for Microsoft Middle East and Africa, he was responsible for 85 countries spanning 4 continents and 10 time zones. Now Chad is a Microsoft MVP.
 
Chad is the chair of several popular open source projects including Indy and Cosmos (C# Open Source Managed Operating System).
 
Chad is the author of the book Indy in Depth and has contributed to several other books on network communications and general programming.
 
Chad has lived in Canada, Cyprus, Switzerland, France, Jordan, Russia, Turkey, and the United States. Chad has visited more than 60 countries, visiting most of them several times.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionDesign questionmemberheartbreaka10 Dec '12 - 22:41 
In IOBase class you say "Read methods require a dummy value". Yes it is true, cause read methods are static and cannot be overridden in base classes. Why don't you make that methods instance methods so you can use abstract keyword and avoid that kludge?
 
My question is, why are that method static? Is it really needed in your design?
AnswerRe: Design questionmemberChad Z. Hower aka Kudzu11 Dec '12 - 15:14 
Id have to look, but IIRC it is to save the compiler from having to keep them all in memory or loading and unloading constantly.
Chad Z. Hower, a.k.a. Kudzu
"Programming is an art form that fights back"
 
My Technical Stuff:
http://www.KudzuWorld.com
 
My Blogs:
http://www.KudzuWorld.com/blogs/

GeneralMy vote of 5memberGerallt G. Franke20 Jul '11 - 3:09 
Really shows the shear power of the .Net Framework, Visual Studio, and good Abstraction techniques.
Once again the Cosmos community have created a masterpiece!
QuestionSecurity of plugs?memberjohndw9418 Jul '11 - 17:58 
What is the security mechanism for using plugs? It looks like it would be fairly easy to inject malicious code into a program by replacing a critical class with a plug.
AnswerRe: Security of plugs?memberChad Z. Hower aka Kudzu18 Jul '11 - 18:00 
Currently there is none, because Cosmos has none (yet). In the future it will be enforced by IL2CPU (the compiler of sorts).
Chad Z. Hower, a.k.a. Kudzu
"Programming is an art form that fights back"
 
My Technical Stuff:
http://www.KudzuWorld.com
 
My Blogs:
http://www.KudzuWorld.com/blogs/

GeneralRe: Security of plugs?memberjohndw9419 Jul '11 - 13:53 
Ok, I think plugs are probably a good solution to the problem but will require very careful implementation for an actual system. One of the biggest benefits of this type of OS is enhanced security by the nature of the design. Has any work been done yet on multi-threading and multi-process scheduling?
GeneralRe: Security of plugs?memberChad Z. Hower aka Kudzu19 Jul '11 - 13:55 
No implementation yet. Its not ready for it. Everyone is eager for graphics, networking and more. But we insist on continued investment in the foundation. If we had jumped ahead to "the fun stuff" we would not have an integrated VS debugger, etc and developers would still be debugging with GDB.
 
In good time....
Chad Z. Hower, a.k.a. Kudzu
"Programming is an art form that fights back"
 
My Technical Stuff:
http://www.KudzuWorld.com
 
My Blogs:
http://www.KudzuWorld.com/blogs/

GeneralRe: Security of plugs?memberChad Z. Hower aka Kudzu19 Jul '11 - 13:59 
Imagine developing a WPF app with no debugger.... How productive would VS be?
Chad Z. Hower, a.k.a. Kudzu
"Programming is an art form that fights back"
 
My Technical Stuff:
http://www.KudzuWorld.com
 
My Blogs:
http://www.KudzuWorld.com/blogs/

GeneralRe: Security of plugs?memberjohndw9422 Jul '11 - 9:31 
I completely agree that debugging is critical. I do a lot of embedded development and when something goes wrong it is really really hard to deduce what the problem is without a debugger.
Questionstunningmemberjpmik4 Jul '11 - 14:24 
This is awesome, I hope you can evolve this into a full graphical OS, even though that will require a staggering amount of work and uh.. willpower Smile | :)
 
Did you get any reactions from Microsoft yet regarding Cosmos?
AnswerRe: stunningmemberChad Z. Hower aka Kudzu4 Jul '11 - 15:42 
Thanks. Orvid already has graphics working as a demo and later we will integrate it. Graphics are cake compared to what we have done already.
 
Re Microsoft - not officially but lots of interest from within. I used to work for Microsoft and I've had lots of contact from people in Microsoft regarding Cosmos. For them its a great demonstration of .NET and Visual Studio.
Chad Z. Hower, a.k.a. Kudzu
"Programming is an art form that fights back"
 
My Technical Stuff:
http://www.KudzuWorld.com
 
My Blogs:
http://www.KudzuWorld.com/blogs/

GeneralRe: stunningmemberChad Z. Hower aka Kudzu4 Jul '11 - 15:45 
For example:
http://cosmos.codeplex.com/SourceControl/list/changesets[^]
 
14 checkins today alone
Chad Z. Hower, a.k.a. Kudzu
"Programming is an art form that fights back"
 
My Technical Stuff:
http://www.KudzuWorld.com
 
My Blogs:
http://www.KudzuWorld.com/blogs/

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 4 Jul 2011
Article Copyright 2011 by Chad Z. Hower aka Kudzu
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid