Visual Studio: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
No edit summary
m (Fixing broken links)
Line 3: Line 3:
However, keep in mind that all Visual C++ is able to output is [[OMF]] and [[PE Binaries|PE]] formats, so you will either have to use [[GRUB]], a custom bootloader, or strip the PE headers in order to make it work.
However, keep in mind that all Visual C++ is able to output is [[OMF]] and [[PE Binaries|PE]] formats, so you will either have to use [[GRUB]], a custom bootloader, or strip the PE headers in order to make it work.


See also [http://ksrenevasan.blogspot.com/2005/10/writing-multiboot-pe-kernels-using.html|Kaushik Srenevasan's blog] to see how you can "[Write] multiboot PE kernels using Visual C++". This is supplemented with [http://ksrenevasan.blogspot.com/2005/10/writing-multiboot-pe-kernels-using_03.html|part 2] which explains some of the things not covered in the first part.
See also [http://ksrenevasan.blogspot.com/2005/10/writing-multiboot-pe-kernels-using.html Kaushik Srenevasan's blog] to see how you can "[Write] multiboot PE kernels using Visual C++". This is supplemented with [http://ksrenevasan.blogspot.com/2005/10/writing-multiboot-pe-kernels-using_03.html part 2] which explains some of the things not covered in the first part.


MinGW32's objcopy should do the job (parametars go something like this: -x -g -X -S -Obinary kernel.bin). But you don't have to strip any information from PE file, the other option is to set section aligment in memory to 0x200 which is generally equal to the section aligment on disk and do some math:
MinGW32's objcopy should do the job (parametars go something like this: -x -g -X -S -Obinary kernel.bin). But you don't have to strip any information from PE file, the other option is to set section aligment in memory to 0x200 which is generally equal to the section aligment on disk and do some math:
Line 11: Line 11:
</pre>
</pre>


where es is loaded with base address of your [[Pe Binaries|PE]] file in memory, ecx will be loaded with the entry point relative to base address of PE file. Of course, if this is protected mode with a flat 4GB address space, use a register instead of using ES.
where es is loaded with base address of your [[PE Binaries|PE]] file in memory, ecx will be loaded with the entry point relative to base address of PE file. Of course, if this is protected mode with a flat 4GB address space, use a register instead of using ES.





Revision as of 14:53, 31 March 2007

Yes, it is possible to use Microsoft Visual C++ to write OS kernel. One of our guests on [Forum:7848 the forum] even provided us a XML configuration file that will suit those needs, as well as 64-bit support code normally provided by the C runtime.

However, keep in mind that all Visual C++ is able to output is OMF and PE formats, so you will either have to use GRUB, a custom bootloader, or strip the PE headers in order to make it work.

See also Kaushik Srenevasan's blog to see how you can "[Write] multiboot PE kernels using Visual C++". This is supplemented with part 2 which explains some of the things not covered in the first part.

MinGW32's objcopy should do the job (parametars go something like this: -x -g -X -S -Obinary kernel.bin). But you don't have to strip any information from PE file, the other option is to set section aligment in memory to 0x200 which is generally equal to the section aligment on disk and do some math:

	mov eax, [es:0x3c]          ; PE header pointer in MZ header
	mov ecx, [es:eax+0x28]      ; AddressOfEntryPoint in PE header

where es is loaded with base address of your PE file in memory, ecx will be loaded with the entry point relative to base address of PE file. Of course, if this is protected mode with a flat 4GB address space, use a register instead of using ES.



Note: The options and procedures described here are for VS.NET 2003. Similar procedures should work on earlier versions. Consult the MSDN or post to the forum if you have any questions.

Visual C++ vs Visual Studio

Visual C++ refers only to the Microsoft C++ IDE and compiler, where as Visual Studio refers to the entire Microsoft family of compilers and IDEs as a whole. In later version, such as the Visual Studio .NET series, all languages share the same IDE program, but in Visual C++ .NET, compatibility for all other languages are removed completely, except through configuring the IDE to manually use another compiler/assembler.

While the Microsoft compilers are free, Visual Studio is not. However, a free edition of Visual C++ is available, known as Visual C++ 2005 Express is available for download from Microsoft's website (along with Visual Basic, Visual C#, Visual J#, and Visual Web developer), but must be activated (freely via e-mail) to continue use after 30-days.

The Microsoft compiler that is provided with the express edition of Visual C++ 2005 can build either native 32-bit or 64-bit PE files, or .NET assemblies.

Creating the Project:

For the kernel and any drivers, create a Win32 Project and select DLL, empty project. Choose DLL if you want to have a kernel that can export functions using the standard Win32 method. It is relatively simple to use this to export functions for use by device drivers...

Some basic definitions:

#define EXTERN		extern "C"
#define EXPORT		EXTERN __declspec(dllexport)    // exported from DLL
#define IMPORT		EXTERN __declspec(dllimport)    // imported from DLL
#define NAKED		__declspec(naked)		// no prolog or epilog code added
#define NORETURN	__declspec(noreturn)

// Some examples
EXTERN void SomeFunction(int this, int that);
EXPORT int AnotherFunction(unsigned __int64 bigParam);

// In a .cpp file
EXPORT NAKED int AnotherFunction(unsigned __int64)
{
  __asm
  {
    mov eax, dword ptr [esp+4]
    xor eax, dword ptr [esp+8]
    ret
  }
}

I use these to create functions that end up with reasonably undecorated names like _SomeFunction@8 instead of ?@SomeFunction@YAKK000I@@Z (as a __cdecl normal function would be named...) The macros also allow easy import and export from a DLL.


Helpful Compiler Intrinsics:

These work like GCC's inline assembler: they insert the instruction, and the compiler is smart enough to optimize register usage, etc.

EXTERN int _outp(unsigned short, int);
EXTERN unsigned short _outpw(unsigned short, unsigned short);
EXTERN int _inp(unsigned short);
EXTERN unsigned short _inpw(unsigned short);
EXTERN void *_ReturnAddress();

#define outb(prt, val)		_outp(prt, val)
#define inb(prt)		((unsigned char)_inp(prt))
#define outw(prt, val)		_outpw(prt, val)
#define inw(prt)		((unsigned short)_inpw(prt))
#define getRetAddr		_ReturnAddress
#pragma intrinsic(_ReturnAddress, _outp, _inp, _outpw, _inpw)


Compiler Options

Here is the meat of this article. These are the compiler options (right-click project, select properties) that I use for my OS.

General

  • Output Directory: .
Add a post-build step to copy only the real output file to the bin directory. Otherwise VS puts .lib and some linker files there as well.
  • Intermediate Directory: .

C/C++ :: General

  • Additional Include Directories: <set as needed>
  • Debug Information Format: Disabled
At the stage my OS is in, I have no use for PDB files. I am in the process of writing a debugger, though, so in the future this could change.
  • Warning Level: Level 4 (/W4)
  • Detect 64-bit Portability Issues: No
This relies on a special __w64 token in various typedefs. I do not know how to use this with my OS. Just be careful: int and long are still 32-bit if compiling for a x64 target (using VS 2005)

C/C++ :: Optimization

  • Optimization: Minimize Size (/O1)
This is really up to you. For me, space is more important than speed for now, but this can easily be changed. If you are implementing source-level debugging, you might want to disable all optimizations.
  • Global Optimizations: Yes (/Og)
Again, enable only if not using a source-level debugger
  • Favor Size Or Speed: Favor Small Code (/Os)
Set as needed, only if /Og enabled
  • Optimize for Processor: Pentium Pro, II, III (/G6)
Set as needed

C/C++ :: Preprocessor

  • Ignore Standard Include Path: Yes (/X)

C/C++ :: Code Generation

  • Enable String Pooling: Yes (/GF)
Places string literals in a read-only data section. This doesn't mean much for OS code, but enable this ONLY if you do not modify string literals in-place, as this would change it in all instances.
  • Enable Minimal Rebuild: No
Rebuilds seem to go fast enough, and enabling this seems to add 0xCC pad bytes to the EXE, which causes bloating.
  • Enable C++ Exceptions: No
Unless you have an exceptional (pun intended) configuration, these require runtime support and are generally not needed anyways.
  • Basic Runtime Checks: Default
Enabling any runtime checks requires special support code.
  • Struct Member Alignment: 1 Byte (/Zp1)
This is really up to you, but most structs that I have need to be aligned this way. If you choose default (8 byte), you can use #pragma pack(push, 1) and #pragma pack(pop) to adjust the packing. Consult MSDN for more info.
  • Buffer Security Check: No
Again, this requires runtime support code

C/C++ (misc. options)

  • Language
  • Force Conformance in For Loop Scope: Yes (/Zc:forScope)
A Good Idea. Makes the i in for (int i = 0; ...) local to the loop.
  • Output Files
  • Assembler Output: Assembly, Machine Code, and Source (/FAcs)
Outputs the assembly listing of the code to files in a given directory. This is nice for assembly-level debugging, as it has the source code lines nearby.
  • ASM List Location: <directory>\
Make sure there is a terminating \, otherwise VS will try to put everything in one file.
  • Advanced
  • Calling Convention: __stdcall (/Gz)
Again, up to you, but I find the lack of name decoration handy for debugging. Functions declared extern "C" void [[Do Something]](int p1, int p2) show up as _[[Do Something]]@8 rather than ?@[[Do Something]]@YAXZSASD or similar.
  • Command Line
  • /Oy-
Disables frame pointer (EBP) omission, included with optimization for size. This is handy to get stack backtraces in case of a crash.

Linker

  • General
  • Output File: <set as needed>
  • Enable Incremental Linking: No (/INCREMENTAL:NO)
Reduces the bloat of the generated EXE or DLL. Linking seems fast enough, anyways.
  • Additional Library Directories: <set as needed>
  • Input
  • Ignore All Default Libraries: Yes (/NODEFAULTLIB)
Ignores the default libc.lib, libcmt.lib, etc.
  • Debugging
  • Generate Debug Info: No
Until I create a better debugger for my OS, I have no use for this. Set as needed.
  • Generate Map File: Yes (/MAP)
Generates a map file (function name and address) and actually sorts by ascending address, unlike GCC.
  • Map File Name: <set as needed>
  • Optimization
  • References: Eliminate Unreferenced Data
  • Enable COMDAT Folding: Remove Redundant COMDATs
  • Advanced
  • Entry Point: <set as needed>
The linker will complain if it is not __stdcall with 12 bytes of arguments (3 32-bit params), but is only a warning.
  • Fixed Base Address: Generate a relocation section
Images will be relocated later. See below.
  • Command Line
  • /DRIVER
  • /ALIGN: 512
Together, they set the Section Alignment and File Alignment to 512 bytes. My boot loader is not sophisticated enough to handle these being different. The downside is that restrictions (read-only, etc) on sections are meaningless, as they require page-granularity for hopefully obvious reasons.


Bootloader Stuff:

As part of the build process, I use a tool I wrote to rebase all the PE files. The Microsoft rebase utility (and imagehlp api) only works on a 64K granularity, but I want one with page granularity.

My bootsector is aware of the FAT file system and loads the rest of the bootloader off of the floppy disk. This bootloader reads a configuration file, loads the kernel to 0xC0000000, and then loads each driver on consecutive page-aligned addresses after the kernel. It passes an array of these addresses (and filenames) to the kernel, which can then link the images to itself and call their entry points.


The Rebase Utility:

Attached to this thread is the source code for a page-granular rebase utility. It changes the base address of any PE file with a relocation section to the nearest page-aligned address and then removes the relocation section. It compiles under Visual Studio .NET 2003 with default settings successfully, but should work fine on any Microsoft compiler.

To use it, create a text file that contains the relative paths and names of the PE files you wish to rebase, such as

system\kernel.sys
driver\fat.sys
driver\fd.sys
driver\kbdmouse.sys
driver\vga.sys

and call the utility from a batch file or the console as

mvrebase 0xC0000000 rebasefiles.txt

and, in the example given above, it will base kernel.sys at 0xC0000000 and will base subsequent files at the next page-aligned address.