65.9K
CodeProject 正在变化。 阅读更多。
Home

在 VS 中集成编译器/汇编器;在 Visual Studio 2010 中使用 NASM

starIconstarIconstarIconstarIconstarIcon

5.00/5 (11投票s)

2012年6月26日

CPOL

10分钟阅读

viewsIcon

99075

downloadIcon

3029

在 VS 2010 中集成自定义构建工具的简单技术。

Assembly in Action

引言

长期以来,在各种汇编论坛中,用户一直要求将他们的工具集成到 Visual Studio 以及其他 IDE 中。尽管 VS 中已经捆绑了一些工具(如 MASM 和 LICX),但对于其他工具来说,这似乎只是一个愿望清单。在 VS 2010 发布后,旧的规则文件已经过时,为了增强自定义能力,基于 XML 的任务工厂占据了中心位置。与规则相比,这种配置是一种高度定制的目标-任务实体,它在设置属性后调用并执行处理代码。这种方法为开发人员提供了一个选择,可以集成他们自己的工具,并对特定代码执行原子构建操作。在本文中演示的汇编文件的整个构建操作由一组三重配置驱动,即一个 XML 文件(包含显示和选项配置 [属性页 UI])、一个目标文件(包含构建系统的策略)和一个属性表(用于初始化和声明)。

本文仅涵盖为 VS 编写目标文件的最小/基本概念。对于高级配置,您实际上必须参考 MSDN 文档。为了保持初学者的简单性,我将坚持使用类比和功能方法分块描述事物,因此我请求资深人士容忍这一点,并提出更好的方法。

目录

本文有以下目标

  1. 需求收集。
  2. 可用的技术选项。
  3. 理解所选选项。
  4. 创建和实现。

需求收集

需求如下

  • 使 NASM 汇编文件能够在 Visual Studio 2010 或更高版本中被识别
  • 结合汇编代码以用于其他兼容项目,如 C、C++ 项目。
  • 一个基于 GUI 的属性页,用于输入构建操作的参数。

可用的技术选项

在此阶段,为了满足要求,在“新建项目”中添加一个新类型的项目选项,或者在“构建自定义”中添加一个选项,都是可见的选择。选择“新建项目”是一个更繁琐的任务,MSDN 已经有相关的演练。而且,它需要更多的努力才能使其与其他语言编译兼容。“构建自定义”相对更简单,因此被选中。

理解所选选项

回顾需求收集部分,现在可以观察到前两个需求已经通过构建自定义选项得到满足 [第一个在 VS 中默认满足]。对于第三个需求,最好首先创建三个文件,即 nasm.props、nasm.targets 和 nasm.xml。这些文件将决定构建操作的布局、序列和结果。文件的解剖、生理和形态将分别说明。

nasm.xml

此 XML 作为代理,用于在属性页中渲染 UI 控件。它包含一组具有预定义属性的标签集合,其设置值作为参数传递给 UI 设计器,UI 设计器会根据这些值布局界面。下面显示了一个最小的功能配置,后面是解释所包含组件的表格。

<?xml version="1.0" encoding="utf-8"?>
<ProjectSchemaDefinitions xmlns="http://schemas.microsoft.com/build/2009/properties" 
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:sys="clr-namespace:System;assembly=mscorlib">
   <Rule Name="NASM" PageTemplate="tool" 
    DisplayName="Netwide Assembler" Order="200">
 <Rule.DataSource>
 <DataSource Persistence="ProjectFile"   ItemType="NASM" />
 </Rule.DataSource>
 <Rule.Categories>
 <Category   Name="General">
    <Category.DisplayName>
  <sys:String>General</sys:String>
    </Category.DisplayName>
 </Category>
 </Rule.Categories>
 <StringProperty Name="ExecutionDescription" 
  DisplayName="Execution Description" IncludeInCommandLine="False" Visible="False" />
 <EnumProperty Name="Outputswitch" Category="Assembler Options" 
     HelpUrl="http://www.nasm.us/doc/"   DisplayName="Output Switch" 
     Description="Select the type of output format required. 
        Linking Should be disabled for ELF and Binary ,else error will popup">
 <EnumValue Name="0" DisplayName="Object File" Switch="-fwin" />
 <EnumValue Name="1" DisplayName="LINUX ELF FORMAT" Switch="-f elf" />
 </EnumProperty>
 <StringListProperty Name="AssembledCodeListingFile" Category="Assembler Options" 
    DisplayName="Assembled Code Listing File" Description="Generates an assembled code listing file. (-l [file])" 
    HelpUrl="http://www.nasm.us/doc/" Switch="-l "[value]"" />
 <BoolProperty Name="GenerateDebugInformation" Category="Assembler Options" 
   DisplayName="Generate Debug Information" 
   Description="Generates Debug Information. (-g)" 
   HelpUrl="http://www.nasm.us/doc/" Switch="-g" />
<! -- Optionally shown for illustration>
 <IntProperty Name="SectionAlignment" DisplayName="SectionAlignment" 
    Description="The /ALIGN option specifies the alignment of each section within the 
       linear address space of the program. The number argument is in bytes and must be a power of two." 
    Category="Advanced" Switch="ALIGN" Separator=":" 
    F1Keyword="VC.Project.VCLinkerTool.Alignment">   </IntProperty>
 <DynamicEnumProperty   Name="NASMBeforeTargets" Category="General" 
    EnumProvider="Targets" IncludeInCommandLine="False">
 <DynamicEnumProperty.ProviderSettings>
     <NameValuePair Name="Exclude" Value="^NASMBeforeTargets|^Compute" />
 </DynamicEnumProperty.ProviderSettings>
 <DynamicEnumProperty.DataSource>
    <DataSource Persistence="ProjectFile" ItemType="" HasConfigurationCondition="true" />
 </DynamicEnumProperty.DataSource>
 </DynamicEnumProperty>
   </Rule>
   <ItemType Name="NASM" DisplayName="Netwide Assembler" />
   <FileExtension Name="*.asm" ContentType="NASM" />
   <ContentType Name="NASM" DisplayName="Netwide Assembler" ItemType="NASM" />
</ProjectSchemaDefinitions>

标签/属性

Application

备注

ProjectSchemaDefinitions

表示数据驱动的项目架构。

嵌入规则和其他定义的父标签。
规则 它初始化定义,还包含一些基本属性,这些属性塑造了其他属性(如 SwitchPrefix)的行为。
Rule.DataSource 这表示属性的读取和写入的项/位置。其行为可由持久性和 Itemtype 属性进一步决定。
Rule.Categories Category 标签在此标签下定义。
类别 添加到此节点会在属性页的左侧窗格中创建一个新值。已显示一个近似的 HTML 插图来模拟视图。
通用 (Category1)
C/C++ (Category2)
链接器 (Category3)
优化 BoolProperty
输出 EnumProperty
文件名 StringListProperty
StringProperty 此标签创建可编辑文本框,可以是多行的。
EnumProperty 这会创建下拉框,其中填充了 Enum 标签中的值。
StringListProperty 一个简单的文本框控件,其值为 <Edit>,用于弹出另一个文本框。
BoolProperty 带有“是/否”选项的下拉列表
DynamicEnumProperty 动态填充的值,用户无需干预
NameValuePair 更像字典实现,其中使用名称和值对设置属性。 名称不能为空。
DynamicEnumProperty. ProviderSettings 设置提供程序特定的选项集以传递给提供程序。
IntProperty 用于设置整数值。它也是一个文本框控件。
ItemType 指示属性是作为 ItemDefinition 元数据还是 Item 元数据存储(取决于属性页的调用方式,即从菜单选项还是从解决方案资源管理器),取决于此项类型。如果未设置此字段,则属性作为公共属性写入 PropertyGroup。
FileExtension 此配置中包含哪些类型的文件将被构建。 使用 ; 分隔扩展名
ContentType 将文件映射到某个 ItemType。
SwitchPrefix 尽管在上面的示例代码中不可见,但它会在命令行中的每个开关前面加上其值。 早期定义的好捷径。
它的语法是
<Rule Name="CL" PageTemplate="tool" SwitchPrefix="/" Order="10"></Rule>

HasConfiguration 简单来说,一个真值表示属性值将应用于整个项目,否则可以为项目中的单个文件设置。
PageTemplate 提供 UI 模板集合中的 UI 模板选择。例如,_tool_ 模板以网格格式渲染属性页右侧窗格。 为了更清晰,请打开本机 C++ 项目的属性页。在“配置属性”类别下,查看
+ 通用 (PageTemplate:generic)
+ 调试 (PageTemplate: debugger)
+ C/C++ (PageTemplate:tool)
类别 表示此属性所属并显示的类别。
持久化 ProjectFile 导致属性值被写入和读取项目清单文件或属性表,具体取决于解决方案资源管理器或“属性”窗口中用于生成属性页 UI 的节点。UserFile 导致属性值被写入和读取 .user 文件。 此字段是强制性的,并且不区分文化。当前接受的值是 ProjectFile 和 UserFile。
Visible 表示控件是否应出现在 UI 上。
DispalyName 此字段中写入的任何内容都直接作为屏幕名称显示在属性页中。 “类别”行中的值 _Optimize,Output,Filename_ 是 DisplayName 的示例。
描述 每当我们单击一个属性时,它都会出现在右侧窗格的底部。
Switch 导致创建整个混乱的罪魁祸首。
它将根据选定的 XXXProperty 将值传递给命令行。
IncludeInCommandLine 是的,这个名字确实暗示了正确的事情,它决定了是否包含在命令行参数中。
HelpUrl 用户可以选择为属性提供一些帮助 URL。
Order 节点(即 Netwide Assembler)在属性页中相对于其他类别的相对位置。
__________________________________________________
Order 值越高,位置越低。

nasm.targets:-

另一个 XML 文件,用于打包目标、任务和其他定义的信息。

一个简单的目标文件由以下内容组成

+ 项层次结构,包括(从父到子):-

+ 目标层次结构

+ 属性层次结构

上述组件用于构建过程。基本的函数单元是任务元素,它封装在目标元素内部。每个目标元素在目标文件中按顺序排列,以便它们可以按顺序执行,任务元素也同样如此,它们依次在目标元素内部排列,以便按其相应位置的顺序依次执行。MSBuild 属性和项都用于将信息传递给任务,评估条件,并存储可在整个项目文件中引用的值。任务提供在构建过程中运行的代码。此外,每个元素都有一组属性,定义了元素的行为。

下面是对整个目标文件(在注释中显示)的逐标签解释

<?xml version="1.0" encoding="UTF-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

   <!--Contains a set of user-defined Item elements. Every item used in a MSBuild project must be specified as a child of an ItemGroup element.-->
   <ItemGroup>
 <!--Property used to add or remove the UI component configuration or simply rules-->
 <PropertyPageSchema Include="$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml" />
 <!--The file or wildcard to include in the list of items.-->
 <AvailableItemName Include="NASM">
 <!--This makes the custom item type names available in the file property's itemtype menu-->
 <Targets>_NASM</Targets>
 </AvailableItemName>
   </ItemGroup>

   <!--A grouping element for individual properties. Properties are specified by using the Property element. They are mainly user defined properties-->
   <PropertyGroup>
 <ComputeLinkInputsTargets>$(ComputeLinkInputsTargets);ComputeNASMOutput;</ComputeLinkInputsTargets>
 <ComputeLibInputsTargets>$(ComputeLibInputsTargets);ComputeNASMOutput;</ComputeLibInputsTargets>
   </PropertyGroup>

   <!--Maps the task that is referenced in a Task element to the assembly(AssemblyName attribute) that contains the task implementation-->
   <UsingTask TaskName="NASM" TaskFactory="XamlTaskFactory" AssemblyName="Microsoft.Build.Tasks.v4.0">
 <Task>$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml</Task>
 <!--Inline Task that contains the task source code.-->
   </UsingTask>

   <!--Contains a set of tasks for MSBuild to execute sequentially.-->
   <Target Name="_NASM" BeforeTargets="$(NASMBeforeTargets)" AfterTargets="$(NASMAfterTargets)" Condition="'@(NASM)' != ''" Outputs="%(NASM.OutputFormat)" Inputs="%(NASM.Identity);%(NASM.AdditionalDependencies);$(MSBuildProjectFile)" DependsOnTargets="_SelectedFiles">
 <ItemGroup Condition="'@(SelectedFiles)' != ''">
 <NASM Remove="@(NASM)" Condition="'%(Identity)' != '@(SelectedFiles)'" />
 </ItemGroup>
 <ItemGroup>
 <NASM_tlog Include="%(NASM.OutputFormat)" Condition="'%(NASM.OutputFormat)' != '' and '%(NASM.ExcludedFromBuild)' != 'true'">
    <Source>@(NASM, '|')</Source>
 </NASM_tlog>
 </ItemGroup>

 <!--This task logs a message during a build.-->
 <Message Importance="High" Text="%(NASM.ExecutionDescription)" />
 <!--This task writes the paths of the specified items to the specified text file.-->
 <WriteLinesToFile Condition="'@(NASM_tlog)' != '' and '%(NASM_tlog.ExcludedFromBuild)' != 'true'" File="$(IntDir)$(ProjectName).write.1.tlog" Lines="^%(NASM_tlog.Source);@(NASM_tlog-&gt;'%(Fullpath)')" />
 <!--Attributes defined on the task, Its good to record all the tools parameters inside the attributes, as it will copy value from the UI during build -->
 <NASM Condition="'@(NASM)' != '' and '%(NASM.ExcludedFromBuild)' != 'true'"
   Inputs="%(NASM.Inputs)"  
   OutputFormat="%(NASM.OutputFormat)"  
   Outputswitch="%(NASM.Outputswitch)"  
   AssembledCodeListingFile="%(NASM.AssembledCodeListingFile)"  
   GenerateDebugInformation="%(NASM.GenerateDebugInformation)"  
   ErrorReporting="%(NASM.ErrorReporting)"  
   IncludePaths="%(NASM.IncludePaths)"  
   PreprocessorDefinitions="%(NASM.PreprocessorDefinitions)"  
   UndefinePreprocessorDefinitions="%(NASM.UndefinePreprocessorDefinitions)"  
   ErrorReportingFormat="%(NASM.ErrorReportingFormat)"  
   TreatWarningsAsErrors="%(NASM.TreatWarningsAsErrors)"  
   floatunderflow="%(NASM.floatunderflow)"  
   macrodefaults="%(NASM.macrodefaults)"  
   user="%(NASM.user)"  
   floatoverflow="%(NASM.floatoverflow)"  
   floatdenorm="%(NASM.floatdenorm)"  
   numberoverflow="%(NASM.numberoverflow)"  
   macroselfref="%(NASM.macroselfref)"  
   floattoolong="%(NASM.floattoolong)"  
   orphanlabels="%(NASM.orphanlabels)"  
   CommandLineTemplate="%(NASM.CommandLineTemplate)"  
   AdditionalOptions="%(NASM.AdditionalOptions)" />
   </Target>
</Project>

 

nasm.props

最后,可以说这个值用于初始化值。更正式地说,props 文件包含属性定义。第一次单击属性时,出现的页面将根据此文件中给出的定义设置各种属性。这个文件中没有太多要解释的,从前面的定义来看,所有工件都显得很明显。

创建与实现

既然已经理解了目标文件的基本原理,是时候创建一个特定于需求的目标文件、其架构和属性定义文件(已在上一节中用于说明元素)。这些文件附在本文中。为了使其全局可用,最好将这些文件(nasm.prop、nasm.targets 和 nasm.xml)复制到 MSBuild 目录(对我来说是“_C:\Program Files\MSBuild\Microsoft.Cpp\v4.0\BuildCustomizations_”)中,即 masm.targets 所在的位置($(VCTargetsPath)\BuildCustomizations\)。

注意:无需设置任何内容即可构建和运行提供的示例。它们已经配置为仅引用其自己的目录。

下面是为轻松构建而采用的配置的快速总结:-

  1. 将 nasm 安装路径添加到 PATH 变量中,或将可执行文件复制到 _%SYSTEMROOT%\System32_ 或其他位置,使其在命令行中只需其名称即可使用。
  2. 将 nasm.prop、nasm.targets 和 nasm.xml 复制到 "_C:\Program Files\MSBuild\Microsoft.Cpp\v4.0\BuildCustomizations\"_(前面已解释)。

现在按照以下步骤创建一个使用 nasm 的示例功能项目。

  1. 创建一个 C++ 项目(最好是空项目);避免托管项目。将其命名为“NASM”。
  2. 在解决方案资源管理器中右键单击项目名称(最顶层的父节点,即 NASM)-> 构建自定义。将弹出一个窗口,其中包含 nasm 选项以及 masm 和 lc(如果您没有将目标文件和属性文件复制到构建自定义目录中,则不会出现此选项,前面已解释过,您必须使用弹出窗口中的“查找现有…”选项手动定位它)。选中 nasm 复选框并单击“确定”。
  3. 再次在解决方案资源管理器中右键单击“源文件”->“添加”->“新建项”-> 选择 C++ 或头文件,输入文件名(Nasm),然后输入扩展名 **.asm**(即 Nasm.asm),然后单击“添加”。再操作一次以添加 Called.asm。
  4. 打开属性页(通过解决方案资源管理器->右键单击 NASM->属性,或选择 NASM 并按 Alt+Enter,或通过菜单中的“项目”->“属性”)。
  5. 在“配置属性”类别中选择“链接器”。展开它并单击“输入”。在“附加依赖项”中添加一个库“libcmt.lib;”。您也可以添加其他库。这只是一个 _C 运行时 (CRT) 库_。
  6. 现在点击左侧窗格中的“高级”选项。是的!!!!当然,我们正在计划一个混乱。在这个时代,我不认为有人会为了打印黑白“Hello World”而学习汇编。所以将“数据执行保护”设置为“否”,这样我们就可以将指令指针弹跳到数据段。

太棒了,设置完成,将以下代码添加到 _Nasm.asm_ 和 _Called.asm_。

Nasm.asm

extern _printf ; the C function, to be called
extern _getchar
extern _extfun
SECTION .data ; Data section, initialized variables
a: dd 5 ; int a=5;
fmt: db "a=%d, eax=%d", 10, 0 ; The printf format, "\n",'0'
msg: db "We are now executing in data area :) With DEP disabled",10,0 
Codev:
push ebp ; set up stack frame
mov ebp,esp
push dword msg ; address of ctrl string
call _printf ; Call C function
add esp, 4 ; pop stack 1 push times 4 bytes
call _extfun
mov esp, ebp ; takedown stack frame
pop ebp ; same as "leave" op
mov eax,0 ; normal, no error, return value
ret

SECTION .text ; Code section.

global _main ; the standard gcc entry point
_main: ; the program label for the entry point
push ebp ; set up stack frame
mov ebp,esp
mov eax, [a] ; put a from memory and store into register
add eax, 2 ; a+2
push eax ; value of a+2
push dword [a] ; value of variable a
push dword fmt ; address of ctrl string
call _printf ; Call C function
add esp, 12 ; pop stack 3 push times 4 bytes
mov esp, ebp ; takedown stack frame
pop ebp ; same as "leave" op 
mov eax,0 ; normal, no error, return value
call Codev ;Time to break into data section
call _getchar ;let us C ;)

ret ; return 

Called.asm

extern _printf
SECTION .data ; Data section, initialized variables
newmsg: db "We are now from data section into an external function ;) ",10,0 


global _extfun ; the standard gcc entry point
_extfun: ; the program label for the entry point
push ebp ; set up stack frame
mov ebp,esp
push dword newmsg ; address of ctrl string
call _printf ; Call C function
add esp, 4 ; pop stack 1 push times 4 bytes
mov esp, ebp ; takedown stack frame
pop ebp ; same as "leave" op 
xor eax,eax ; normal, no error, return value
ret 

现在是行动时间,点击绿色按钮!!构建并运行代码。享受输出。

关注点

不多,但我一月份就开始了,很快就跑了。

这类任务的可用资源稀少且难以搜索。当你的配置把所有东西都搞砸时,情况就更糟了。我也有同样经历。目标文件中的一个小改动就卡住了整个过程。然而,五天前我重新开始研究它,我对汇编的热爱证明了一些事情。

由于时间不足,我无法测试 XML 文件中的完整配置,事实上这是一个社区导向的设计(在这种情况下是 NASM 社区),也许我一个人不足以创建它。因此,始终欢迎提出修改建议。

© . All rights reserved.