Visual Studio

来自osdev
跳到导航 跳到搜索

本页面或章节指其读者或编者使用人称 , 我的, 我们 或者 我们的。 用encyclopedic百科全书式的语言风格进行编辑

Visual Studio可以作为集成开发环境,用于编写F5调试功能齐全的汇编,C和C++。 代码可以使用您通常使用的 GCC_Cross-Compiler 工具链或Microsoft Visual C编译器进行编译和调试。 由于标准的Visual C++编译器只能发射 OMF 目标文件,链接器只能产生 PE二进制文件,如果你追求这个途径,你将不得不使用一个能理解这些格式的引导加载程序,或者将它们转换成,例如,精灵 或平面二进制。

由于Visual Studio中进行本机i686-elf操作系统开发的唯一已知方法依赖于第三方非自由软件,因此本文的大部分内容探讨了如何将Visual Studio的本机功能用于进行操作系统开发。

如果您有兴趣尝试使用Visual C++工具链进行操作系统开发,Kaushik Srenevasan的博客 可以作为一个很好的起点,了解如何 “[编写] 使用Visual C的多引导PE内核”。[1] 对此进行了补充,该部分解释了第一部分中未涵盖的一些内容。

MinGW32的objcopy应该完成这项工作 (参数如下: -x -g -X -S-基本内核.bin)。 但是您不必从PE文件中删除任何信息,另一个选项是将内存中的节对齐设置为0x200,这通常等于磁盘上的节对齐,并做一些数学运算:

 mov eax[es:0x3c]; MZ标头mov ecx中的PE标头指针,[es:eax 0x28]; PE标头中的AddressOfEntryPoint

如果es在内存中加载了您的 PE 文件的基址,则ecx将加载相对于PE文件基址的入口点。当然,如果这是具有平坦4GB地址空间的保护模式,请使用寄存器而不是使用ES。

注意: 此处描述的Visual C++操作系统开发的选项和过程适用于VS.NET 2003。 类似的程序应该在早期版本上工作。如果您有任何疑问,请咨询MSDN或发布到论坛。

原生i686-elf开发

先决条件

为了将Visual Studio用作本机i686-elf操作系统开发的开发环境,您必须首先安装以下先决条件

在尝试使用较新版本之前,尝试使用这些项目的推荐版本是 “强烈推荐”。 特别是,在使用比上面列出的版本新的NASM和QEMU版本时,已经观察到问题 (见下文)。

Install QEMU to C:\Program Files (x86)\qemu, NASM to C:\Program Files (x86)\nasm and extract i686-elf-tools to C:\Program Files (x86)\i686-elf-tools (such that the path to your GCC is C:\Program Files (x86)\i686-elf-tools\bin\i686-elf-gcc.exe)

不幸的是,为了在Visual Studio中进行操作系统开发,您将需要第三方加载项 VisualGDB。与该列表中的所有其他软件不同,VisualGDB不是免费的。 您可以从他们的网站下载VisualGDB的30天试用版,以演示本文中概述的原则并确定您希望如何进行。

安装VisualGDB后首次启动Visual Studio时,系统可能会提示您配置 VISUALGDB_DIR 环境变量 (您应该这样做),以及是否要使用它们基于Clang的IntelliSense引擎 (您可以在此提示上禁用选项)。 如果您有兴趣购买VisualGDB,则似乎需要 “自定义” VisualGDB版本。

This process has been tested with VisualGDB 5.1 in Visual Studio 2015. A sample project demonstrating a basic boot sector and kernel that you can compile, debug and step through can be found here.

配置

下面概述了如何从头开始在Visual Studio中配置构建和调试操作系统。 您也可以移植 (并根据您的项目名称重命名) *。vgdbsettings文件从 [2] 到您的解决方案中,您应该可以使用。

  • 在Visual Studio中,使用 自定义项目向导 创建新的VisualGDB解决方案。 如果提示您配置自定义项目的设置,请单击 ““ 完成 ”” 以将其保留为默认值 (我们将在下面逐一配置它们)
  • 在解决方案资源管理器中右键单击您的项目,然后选择 visualgdb项目属性
构建设置
  • On the Build Settings tab, next to Build command: click Customize and fill in the following details
Command: $(VISUALGDB_DIR)\make.exe
Arguments: all
Working directory: $(SourceDir)
  • Next to Clean Command: click Customize and fill in the following details
Command: $(VISUALGDB_DIR)\make.exe
Arguments: clean
Working directory: $(SourceDir)
  • In the Main binary: field enter the $(BuildDir)\<image> where <image> is the name of the file your Makefile generates that contains your entire operating system. e.g. $(BuildDir)\os-image
The configuration steps outlined in this tutorial assume that all of your output files will be emitted in the most "convenient" place possible (the same place as your source files, the root of your project, etc). If you are interested in having your output files be emitted to a single directory, you should investigate this after you have the basic configuration working.
  • Untick the Try detecting common Makefile types and updating source lists in them option. To begin with we would like to control everything ourselves; you can explore re-enabling options like this once we have the basic configuration working
调试设置
  • On the Debug settings tab, deselect Break-in to GDB using Ctrl-Break events instead of Ctrl-C (required under Cygwin)
  • Next to Use a custom GDB executable: click Customize and fill in the following details
GDB debugger executable: C:\Program Files (x86)\i686-elf-tools\bin\i686-elf-gdb.exe
  • Next to GDB launch command: click Customize and fill in the following details
Arguments: --interpreter mi --readnow
Working directory: $(ProjectDir)
  • Tick the Use a gdbserver: option then click Customize and fill in the following details
Command: C:\Program Files (x86)\qemu\qemu-system-i386.exe
Arguments: -S -gdb tcp::1234,ipv4 -soundhw all -drive file=$(TargetFileName),if=floppy
Working directory: $(ProjectDir)
  • In the Target selection command: field enter -target-select remote :1234
  • Change the Debugging start mode: to Use "continue" command
IntelliSense 设置
  • On the IntelliSense Settings tab, under Clang IntelliSense set the IntelliSense engine: to Use native Visual Studio IntelliSense engine
GDB Settings
  • On the GDB settings tab, untick Support 'step into new instance' through breakpoint in: main
We don't need a breakpoint in any kind of main function, but if you want one at a later point you can re-enable this and change the function name to the real entrypoint of your kernel.
GDB Startup Commands
  • On the GDB startup commands tab, under The following GDB commands will be run AFTER selecting a target: enter the following
 symbol-file kernel.elf
 add-symbol-file boot_sect.elf 0x7c00
 directory $(RemoteSourceDir)/src
These will load the symbols for your kernel and bootsector respectively, allowing you to debug through the sourcecode in your debugger.
If you have all of your source under a src/ subdirectory, it appears that NASM may cause all *.asm files but the main one to be resolved using relative, instead of absolute paths, thus resulting in GDB being unable to find them when attempting to set breakpoints. Specifying directory $(RemoteSourceDir)/src adds the src/ folder as an additional search location for GDB to use when resolving breakpoint locations. If you're not using a src/ folder, then you likely don't need this line

如果您已经成功完成了这些步骤,那么您应该已经准备好开始使用Visual Studio开发操作系统了

Important Considerations

  • When you modify an *.asm and recompile, it doesn't seem to automatically detect such files are modified; as such you may need to force rebuild instead, or investigate how to get your make system detect changes to assembly files
  • When stepping through assembly code, until you switch to 32-bit protected mode you may have issues with your source files not always lining up with where the debugger is currently at. This seems to be an unavoidable consequence of trying to debug 16-bit code; once you start debugging 32-bit assembly or C/C++ however it all seems to be fine
  • When you terminate QEMU, GDB will detect the process was terminated but won't actually end the debug session (as such you'll need to hit the Stop button yourself)
  • If you try and create a Makefile in Solution Explorer with no file extension Visual Studio will probably add a .cpp to the end of it again and move it under the Source files filter. You will probably want to remove the extension again and maybe move it under the project root, outside the Source files filter
  • Remember that in C++ projects in Visual Studio, folders in Solution Explorer simply represent "filters" rather than actual folders; as such as you start organizing your files remember to place your new files under the actual folder they belong in, along with organizing them under the correct filter
  • QEMU versions newer than the stipualted version may experience errors when the recommended version of GDB 8.0 attempts to attach to them. If you wish to use a newer QEMU version, you will need to investigate whether modifying your QEMU command line arguments or compiling a newer GDB version resolves this issue
  • NASM versions newer than the stipulated version may not generate symbol files properly, resulting in breakpoints in %include'd assembly files always hitting the last line of the file. If you wish to use a newer NASM version, you will need to investigate whether modifying your NASM command line arguments or compiling a newer GDB version resolves this issue

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.

虽然微软编译器是免费的,但Visual Studio不是。 但是,可以从Microsoft网站下载免费的Visual Studio版本,称为 Visual Studio 2013 Express,但必须激活 (通过电子邮件自由) 才能在30天后继续使用。

Visual C++ 2013的express版提供的Microsoft编译器可以构建本地32位或64位PE文件,或者。NET程序集。

当然,您可以禁用编译器并添加自定义构建事件,该事件运行shell脚本并调用Cygwin。 如果您使用的是Visual Studio (不是Express),则可以使用Visual Studio SDK创建一个 “makefile项目”,该项目允许您使用自定义构建脚本 (例如调用 Cygwin 工具链) 来编译您的代码,构建您的图像,并启动仿真器 (因此您可以按F5进行整个构建,然后启动仿真器)。 在启用调试的情况下编译Bochs可能允许您使用Visual Studio调试器 (包括逐行执行),但尚未对此进行测试。

创建项目:

对于内核和任何驱动程序,创建一个Win32项目,然后选择DLL,空项目。 如果要拥有可以使用标准Win32方法导出函数的内核,请选择DLL。 使用它来导出供设备驱动程序使用的功能相对简单...

Custom C++ Runtime

由于您不能在内核中使用标准C/C运行时,因此您需要自己编写一些功能。 下面的文章将有助于 write your custom Visual C++ runtime

一些基本定义:

#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.

Compiler Options

这是本文的内容。 这些是我用于我的操作系统的编译器选项 (右键单击项目,选择属性)。

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>
  • 调试信息格式: 禁用
在我的操作系统所处的阶段,我不使用PDB文件。 不过,我正在编写调试器,因此将来可能会发生变化。
  • 警告级别: 4级 (/W4)
  • 检测64位可移植性问题: 否
这依赖于各种typedefs中的特殊 __w64令牌。 我不知道如何在我的操作系统中使用它。 请注意: 如果为x64目标编译,int和long仍然是32位 (使用VS 2005)

C/C++ :: Optimization

  • Optimization: Minimize Size (/O1)
This is really up to you. 对我来说,空间现在比速度更重要,但这很容易改变。 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++ :: 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
This option attempts to analyse header files and only rebuilds sources if what it uses has changed. Can speed up building, but also frequently makes mistakes leading to runtime errors. Also 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

作为构建过程的一部分,我使用我编写的工具来重新建立所有PE文件。 Microsoft rebase实用程序 (和imagehlp api) 仅在64k粒度上工作,但我想要一个页面粒度。

我的引导部门知道FAT文件系统,并从 软盘。 此引导加载程序读取一个配置文件,将内核加载到0xC0000000, 然后在内核之后的连续页面对齐地址上加载每个驱动程序。 它通过了 这些地址 (和文件名) 的数组到内核,然后可以链接图像 并称之为切入点。

Another option is to use a separate linker such as WLink with a linker script such as the one found on the Watcom page.

Multiboot

要由GRUB引导,您可以使内核多引导。这涉及在映像的前8k中嵌入multiboot标头。 This can be done as follows:

//Entry point goes here

//The good ol' multiboot header
#pragma pack(push,1)
struct MULTIBOOT_HEADER {
    uint32_t magic;
    uint32_t flags;
    uint32_t checksum;
    uint32_t header_addr;
    uint32_t load_addr;
    uint32_t load_end_addr;
    uint32_t bss_end_addr;
    uint32_t entry_addr;   
};
#pragma pack(pop)

#pragma code_seg(".a")
__declspec(align(4)) MULTIBOOT_HEADER header {
    0x1BADB002,
    0x10003,
    -(0x1BADB002+0x10003),
    (uint32_t)&header - BASE_ADDR + LOADBASE,
    LOAD_BASE,
    0,
    0,
    (uint32_t)&entry - BASE_ADDR + LOADBASE
};

The Rebase Utility

附在 this thread 上的是页面粒度rebase实用程序的源代码。 它将任何带有重定位部分的PE文件的基址更改为最近的页面对齐地址,然后删除重定位部分。 它在Visual Studio下编译。NET 2003默认设置成功,但在任何Microsoft编译器上都可以正常工作。

要使用它,请创建一个文本文件,其中包含要重新建立基础的PE文件的相对路径和名称,例如

 系统 \ 内核.sys驱动程序 \ fat.sys驱动程序 \ fd.sys驱动程序 \ kbdmouse.sys驱动程序 \ vga.sys 

,并从批处理文件或控制台以

 mvrebase 0xC0000000 rebasefiles.txt 

的身份调用该实用程序,在上面给出的示例中,它将以0xC0000000为基础的kernel.sys,并将以下一页对齐地址为基础的后续文件。

Express 64 bit compilers

您可以通过安装Windows SDK VC编译器获得64位编译器 (请注意VC10 SP1您需要安装更新)

怪癖

在64位编译器中,您 “不能:”

  • 创建naked functions
  • 进行内联汇编-但它确实与 MASM 一起提供,尽管您需要将C/C++和程序集分离为单独的源文件。
  • 进行非快速呼叫

这就是为什么如果你打算在MSVC++中进行64位开发,你应该有一个外部汇编层 (32位和64位的单独版本),或者使用更有限的内部函数,对于asm,如果你想避免名称装饰,你需要在这样的头文件中声明它们:

#ifdef __cplusplus //if this is C++
extern "C" { //declare as C functions
#endif
 disable(); //a useful example. disables interrupts (duh!)
 //your functions go here
#ifdef __cplusplus
} //and if it was C++ we need to close the brackets
#endif

And in your asm layer:

BITS 32 ;32 bit version
@disable@0:
cli
ret 
;fastcall name decoration (@0 to be replaced by size of args (bytes)
;Number of bytes is always prefixed by @
BITS 64 ;64 bit version
disable:
cli
ret  ;No decoration at all

Intrinsics

对于intrinsics, #include <intrin.h>. 这适用于内核,但不要忽略标准标头。示例如下:

//main body of intrinsics
#include <intrin.h>
//I/O operations
#include <conio.h>

void someFunc()
{
    __enable();  //STI
    char c = __inbyte(0x60); //IN
    unsigned short w = __inpw(0x1F0);
    unsigned int d = __inp(0xCFC);
    __disable();  //CLI
    __halt();     //HLT
}

从更多的内在特征中,请参见 Compiler Intrinsics 请注意,在MSDN的某些区域中引用的非常有用的 __setreg不再可用。

另见

论坛帖子