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





5.00/5 (11投票s)
在 VS 2010 中集成自定义构建工具的简单技术。
引言
长期以来,在各种汇编论坛中,用户一直要求将他们的工具集成到 Visual Studio 以及其他 IDE 中。尽管 VS 中已经捆绑了一些工具(如 MASM 和 LICX),但对于其他工具来说,这似乎只是一个愿望清单。在 VS 2010 发布后,旧的规则文件已经过时,为了增强自定义能力,基于 XML 的任务工厂占据了中心位置。与规则相比,这种配置是一种高度定制的目标-任务实体,它在设置属性后调用并执行处理代码。这种方法为开发人员提供了一个选择,可以集成他们自己的工具,并对特定代码执行原子构建操作。在本文中演示的汇编文件的整个构建操作由一组三重配置驱动,即一个 XML 文件(包含显示和选项配置 [属性页 UI])、一个目标文件(包含构建系统的策略)和一个属性表(用于初始化和声明)。
本文仅涵盖为 VS 编写目标文件的最小/基本概念。对于高级配置,您实际上必须参考 MSDN 文档。为了保持初学者的简单性,我将坚持使用类比和功能方法分块描述事物,因此我请求资深人士容忍这一点,并提出更好的方法。
目录
本文有以下目标
- 需求收集。
- 可用的技术选项。
- 理解所选选项。
- 创建和实现。
需求收集
需求如下
- 使 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 插图来模拟视图。 |
|
|||||||||||||
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->'%(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\)。
注意:无需设置任何内容即可构建和运行提供的示例。它们已经配置为仅引用其自己的目录。
下面是为轻松构建而采用的配置的快速总结:-
- 将 nasm 安装路径添加到 PATH 变量中,或将可执行文件复制到 _%SYSTEMROOT%\System32_ 或其他位置,使其在命令行中只需其名称即可使用。
- 将 nasm.prop、nasm.targets 和 nasm.xml 复制到 "_C:\Program Files\MSBuild\Microsoft.Cpp\v4.0\BuildCustomizations\"_(前面已解释)。
现在按照以下步骤创建一个使用 nasm 的示例功能项目。
- 创建一个 C++ 项目(最好是空项目);避免托管项目。将其命名为“NASM”。
- 在解决方案资源管理器中右键单击项目名称(最顶层的父节点,即 NASM)-> 构建自定义。将弹出一个窗口,其中包含 nasm 选项以及 masm 和 lc(如果您没有将目标文件和属性文件复制到构建自定义目录中,则不会出现此选项,前面已解释过,您必须使用弹出窗口中的“查找现有…”选项手动定位它)。选中 nasm 复选框并单击“确定”。
- 再次在解决方案资源管理器中右键单击“源文件”->“添加”->“新建项”-> 选择 C++ 或头文件,输入文件名(Nasm),然后输入扩展名 **.asm**(即 Nasm.asm),然后单击“添加”。再操作一次以添加 Called.asm。
- 打开属性页(通过解决方案资源管理器->右键单击 NASM->属性,或选择 NASM 并按 Alt+Enter,或通过菜单中的“项目”->“属性”)。
- 在“配置属性”类别中选择“链接器”。展开它并单击“输入”。在“附加依赖项”中添加一个库“libcmt.lib;”。您也可以添加其他库。这只是一个 _C 运行时 (CRT) 库_。
- 现在点击左侧窗格中的“高级”选项。是的!!!!当然,我们正在计划一个混乱。在这个时代,我不认为有人会为了打印黑白“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 社区),也许我一个人不足以创建它。因此,始终欢迎提出修改建议。