当前位置: 首页 > 编程日记 > 正文

汇编程序设计与计算机体系结构软件工程师教程笔记:内联汇编与宏

《汇编程序设计与计算机体系结构: 软件工程师教程》这本书是由Brain R.Hall和Kevin J.Slonka著,由爱飞翔译。中文版是2019年出版的。个人感觉这本书真不错,书中介绍了三种汇编器GAS、NASM、MASM异同,全部示例代码都放在了GitHub上,包括x86和x86_64,并且给出了较多的网络参考资料链接。这里只摘记了MASM和NASM,测试代码仅支持Windows和Linux的x86_64。

9. 内联汇编与宏

9.2 内联汇编:是一种在高级语言中嵌入汇编代码的办法。还有一种办法也能把汇编语言的代码同高级语言的代码结合起来,就是用汇编代码来撰写函数,并将其放在一份文件中,然后在C++代码中调用这些函数。不过,这种办法需要执行相关的准备工作,而且还涉及函数调用,因此会引发一些开销。与之相比,撰写内联式的汇编代码在某些情况下可能更为简单。

各种编译器会采用不同的做法来处理内联式的汇编代码。如果高级语言的代码所内嵌的汇编代码在编译过程中没有受到修改或优化,那么GCC实际上就相当于把这一部分汇编代码复制到它所输出的汇编文件中(这种文件的后缀名是.s),进而把这些代码与从高级语言转换而来的那些汇编代码一起交给GNU汇编器(GNU Assembler, 也就是GAS)处理。Microsoft的Visual Studio采用Visual C++内置的汇编器处理内联汇编语句,而不会专门采用MASM那样单独的汇编器来做。Microsoft的x64 C/C++编译器不支持内联式的汇编代码,它改用编译器内部函数来完成相应的底层指令

C++编译器通常用asm或__asm关键字表示内联式的汇编语句。内联汇编代码范例如下表所示:

Clang/GCC汇编器要求asm关键字后面必须写一对圆括号及一个分号,而汇编代码则要以字符串的形式写在一对双引号中,并放置在这对圆括号中。换行符与制表符用来分割字符串中的各条汇编语句Visual C++要求__asm关键字的后面要么直接写汇编语句,要么跟上一对花括号。如果汇编语句只有一条可以直接写出来,若有很多条则需放在花括号中。最后的分号可有可无

开发者在使用Clang/GCC时可能会开启-ansi或-std这样的编译选项,从而导致asm及inline等关键字遭到禁用,这种情况下,可以把asm关键字写成__asm__。

Clang与GCC是有基本形式与扩展形式之分的。尽量不要使用基本形式的asm内联,因为在访问全局变量时可能要做不同的处理,而且,这种形式还会假设汇编代码不修改通用的寄存器。然而基本形式的asm内联也有一些好处,由于它不一定非要写在C/C++函数中,因此可以用来执行汇编器指令并撰写全局的汇编函数。此外,如果C/C++函数声明成了naked函数,编译器就不会为其生成开场及收场代码(prologue and epilogue code),这就要求开发者必须使用基本形式的asm内联来为这样的函数撰写定制的开场及收场代码。扩展形式的asm内联可以更精细地控制与汇编代码有关的输出与输入,并指明代码所用到的寄存器。Clang/GCC要求扩展形式的asm代码必须写在C/C++函数中,Visual C++则要求所有的内联汇编代码都必须这样写。

GCC并不会解析内联式的汇编指令,因此它根本就不知道这些语句是否有效,而且GDB等调试器也不会单步地进入内联汇编语句中去调试,而是把整个asm视为一个步骤。如果要单步调试,可以在想插入断点的地方手工添加”int $3”指令。Visual Studio无须使用INT即可单步调试x86内联汇编指令。

内联汇编中的注释既可以按汇编代码自身的格式来写也可以按C++的格式来写,建议采用后一种写法。

Visual C++可以直接访问C语言式的变量。Clang/GCC访问变量的方式比较复杂,对内联的汇编代码来说,用于输出和用于输入的变量都必须在模板中的相应列表中说明。当然,列表也可以留空,如果其中有内容,那么与clobbers列表合计起来不得超过30项。

在Clang/GCC的内联式汇编代码中对涉及输出与输入的变量做出说明:

[asmSymbolicName] “constraint” (CvariableName)

其中,asmSymbolicName可以是任何一个有效的标识符名称,它也可以与外围代码中已经定义的C语言标识符同名,从而使内联式的汇编代码能够用同样的名称来引用这个变量。constraint(约束或限定)这一部分用来指定与操作数的用法及放置地点有关的信息。它可以是一个或一系列字面。如果某个操作数是用来输出数值的,那么它必须先以修饰符开头,然后才能写上一个或多个字母。最常用的两个修饰符是”=”与”+”,前者表示该操作数在执行汇编指令之前的初始值并不重要,仅仅是供汇编代码写入数据的,后者表示汇编代码既要读取该操作数的初始值,又要向其中写入数据(也就是说这个参数既用于输入又用于输出)。有的时候,系统会先把那些仅仅用于输入的参数处理完,然后再处理那些供汇编代码输出数据的参数,这就促使它有可能会复用某些寄存器,令其在不同的阶段表示不同的参数。这样一来,如果其中有某个输出参数在处理输入参数的过程中已经写进了值,那么这个值就会把用同一个寄存器所表示的输入参数所具备的初始值给覆盖掉。为了避免这种覆写的情况,可以在”=”或”+”后面写上”&”符号,使得系统能够正确地实现这样的参数。

在Clang/GCC的内联汇编模板中,还有一个部分叫做clobbers列表,列表中需要写上内联的汇编代码所修改的寄存器,以防止系统误以为这些寄存器的值不会为汇编代码所修改,从而执行一些相关的优化措施。系统可能会生成指令来保存它们的值,以便在执行完内联的asm语句后能够予以恢复。系统在选用寄存器来实现汇编代码中用于输入及输出的操作数时会避开clobbers列表中指明的项目。如果你所写的内联式汇编代码确实会顺带影响到某个寄存器的值,但你却没有将其写在clobbers列表中,那么程序可能会出错或发生意外的结果。

下表列出了内联式汇编代码中经常用到的x86约束修饰符:

根据AT&T汇编语法,寄存器以一个%符号开头。不过,内联式的汇编代码中寄存器需要用两个%符号开头,也就是要在前面加上%%,若只加一个%,表示的则是这段内联代码所用的参数(或变量)。之所以要这样写,是因为内联代码会把%当作转义符使用,以便输出它后面的符号所表示的字符,因此,”%%”输出的是单个%字符,而”%=”则会针对代码库中的每一段asm代码输出一个独特的数字(这个数字可以用来创建标签,以供其它代码引用)。此外,”%{”、”%|”及”%}”这三种写法分别用来输出{、|及}字符。这三种字符必须用%来转义,假如直接写在内联的汇编代码中,那么系统就会将其当成特殊字符并解读成用来表示各种汇编方言的结构。如果要产生%eax这样的汇编代码,那么在内联的汇编语句中应该写成%%eax,不过,在clobbers列表中提到eax时,只写一个%就好。

Clang与GCC默认使用AT&T语法规则,而Visual C++则使用Intel语法规则。不过,Clang与GCC能够在内联的汇编代码中使用多种汇编方言。由AT&T语法切换到Intel语法能够使代码变得好懂一些。要想采用某种特定的语法来撰写内联汇编代码,一种简单的办法是在第一条命令中写上.att_syntax以表示这段代码用的是AT&T语法,或者写上.intel_syntax以表示这段代码用的是Intel语法。Clang与GCC可以通过-masm这个编译选项来指定内联汇编代码所采用的方言。-masm=att与-masm=intel分别表示AT&T语法及Intel语法。

9.3 宏(macro):它可以视为一个模块或一系列指令,并根据名称来加以调用。汇编代码中的宏的运作方式与C++代码中的内联函数类似,都是用其中所包含的一系列语句或指令来替换位于调用点的宏名或函数名。每调用一次宏就要发生一次代码替换或代码展开。

对比宏与函数:函数适合实现那种比较长而且需要频繁执行的代码。无论函数调用多少次,都只需要在程序中保留一份代码。每次调用函数时,系统会把控制权转移给内存中的这个函数。调用函数涉及传递参数、建栈以及清理栈等工作,从而会产生一定的开销。为了提高效率,这些比较长且比较复杂的函数代码只在程序中保留一份就可以了。宏适合实现那种比较短而且需要频繁执行的代码。对程序做汇编的时候,汇编器每看到一个宏就会把该宏所代表的那一系列指令展开到这里,于是同样一段代码就有可能多次出现在程序中,这样做的好处是省去了调用函数所需的开销。宏与函数有一个共同点,就是都可以接受参数(argument或parameter),不过函数的参数是在运行程序的时候才传递过去的,而宏的参数则是在做汇编的时候就已经替换好了。

定义并调用宏:NASM采用[%#]的格式来指代参数,其中的#部分是从1开始的编号;MASM直接按照参数的名称来引用。宏可以写在程序段以外,也可以写在其中,无论采用哪种写法都应该在整个程序的范围内保持一致。还有一种管理宏的办法,是把所有的宏或者相关的一组宏单独放在一份文件中,然后通过INCLUDE汇编命令将其包含进来。MASM的宏还具备一项特性,就是可以在参数后面写上:REQ,以要求调用方必须指定这个参数而不能将其忽略。

以下是测试内联汇编和宏的测试代码段,这些code主要来源本书的示例代码:

MASM的内联汇编代码段如下:

int test_inline_int()
{__m128i var1 = _mm_cvtsi32_si128(1234);__m128i var2 = _mm_cvtsi32_si128(2);var1 = _mm_add_epi64(var1, var2);int output = _mm_cvtsi128_si32(var1);fprintf(stdout, "output: %d\n", output);return 0;
}

以上代码段是在Visual Studio下执行两个整数的相加,向_mm_add_epi64等这些指令来自于SSE2 Intrinsics,执行结果如下图所示:

NASM的内联汇编代码段如下:

int test_inline_int()
{int var1 = 1234, var2;asm ("mov %1,%%eax \n\t""add $2,%%eax \n\t""mov %%eax,%0 \n\t":"=r" (var2)       /* %0: Out */:"r" (var1)        /* %1: In */:"%eax"            /* Overwrite */);fprintf(stdout, "var2: %d\n", var2);return 0;
}

以上代码段是在Linux下执行两个整数的相加,执行结果如下图所示:

MASM的宏代码段如下:

extrn ExitProcess : proc_printInt  PROTO CintAdd MACRO dest, source1, source2mov rax, source1add rax, source2mov dest, rax
ENDM.DATA
intA QWORD 2
intB QWORD 4
intC QWORD 3
intD QWORD 7
result QWORD 0.CODE
macro_usage PROCpush rbpintAdd result, intA, intBmov rcx, resultmov rax, 0call _printIntintAdd result, intC, intDmov rcx, resultmov rax, 0call _printIntpop rbpret
macro_usage ENDP
END

执行结果如下图所示:

NASM的宏代码段如下:

extern _printInt%macro intAdd 3mov rax, [%2]add rax, [%3]mov [%1], rax
%endmacrosection .data
intA: dq 2
intB: dq 4
intC: dq 3
intD: dq 7
result: dq 0section .text
global macro_usage
macro_usage:push rbp ; set up stack frame, must be allignedintAdd result, intA, intBmov rdi, qword [result]mov rax, 0 ; or can be xor rax, rax, no vector registers in usecall _printIntintAdd result, intC, intDmov rdi, qword [result]mov rax, 0call _printIntpop rbp ; restore stackmov rax, 0 ; normal exit, no error, return valueret ; return to operating system

执行结果如下图所示:

GitHub:https://github.com/fengbingchun/CUDA_Test

相关文章:

无需标注数据,利用辅助性旋转损失的自监督GANs,效果堪比现有最好方法

作者 | Ting Chen译者 | 王红成出品 | AI科技大本营(ID:rgznai100)本文作者提出了一种自检督方式的生成对抗网络,通过辅助性的旋转损失来达到目的。因为通常主流方法来生成自然图像都是通过条件GAN来完成,但是这就需要很多的标签数…

iOS环信聊天界面中点击头像和消息的几种状态

/*环信自带头像点击事件*/ - (void)messageViewController:(EaseMessageViewController *)viewControllerdidSelectAvatarMessageModel:(id<IMessageModel>)messageModel {内容可以根据需要自己添加 }/*!methodbrief 点击了简历消息 (lyq添加)discussion 点击了简历消息,…

ASP.NET将Session保存到数据库中

因为ASP.NET中Session的存取机制与ASP相同&#xff0c;都是保存在进行中&#xff0c; 一旦进程崩溃&#xff0c;所有Session信息将会丢失&#xff0c;所以我采取了将Session信息保存到SQL Server中&#xff0c;尽管还有其它的几个方式&#xff08;本文不作介绍&#xff09;&…

iOS App上架流程

一、前言&#xff1a;作为一名iOSer&#xff0c;把开发出来的App上传到App Store是必要的。下面就来详细讲解一下具体流程步骤。 二、准备&#xff1a; 一个已付费的开发者账号&#xff08;账号类型分为个人&#xff08;Individual&#xff09;、公司&#xff08;Company&#…

不止Markov决策过程,全景式分析强化学习研究内容

作者 | 肖智清编辑 | 刘静来源 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;强化学习作为通用人工智能的希望&#xff0c;吸引了很多人工智能爱好者学习和研究。Markov决策过程是最知名的强化学习模型&#xff0c;强化学习教程也常以Markov决策过程作为起点。但是&am…

Windows下创建进程简介

正在执行的应用程序称为进程&#xff0c;进程不仅仅是指令和数据&#xff0c;它还有状态。状态是保存在处理器寄存器中的一组值&#xff0c;如当前执行指令的地址、保存在内存中的值&#xff0c;以及唯一定义进程在任一时刻任务的所有其他值。进程与应用程序的一个重要的区别在…

jQuery中鲜为人知的的几个方法

转来学习一下 jQuery中鲜为人知的的几个方法 jQuery近些年来仍旧是web开发中最受欢迎的类库&#xff0c;虽然大家褒贬不一&#xff0c;但是仍旧不失为一款最流行的Javascript&#xff0c;在今天这篇文章中&#xff0c;我们将介绍几个jQuery的相关方法&#xff0c;无论你是入门级…

Linux下创建进程简介

在博文https://blog.csdn.net/fengbingchun/article/details/108940548中简单介绍了Windows下通过函数CreateProcess创建进程的过程&#xff0c;这里简单介绍下Linux下通过fork函数创建进程的过程。很早之前在https://blog.csdn.net/fengbingchun/article/details/45690745中也…

热更新 FrameWork

工作中遇到想要绕过AppStore直接更新App的要求&#xff01;这里友情提示下&#xff0c;看了很多资料只是是实现了功能&#xff0c;但在项目中并没有真正用到&#xff01;资料大多都显示会被拒&#xff0c;这个说的是个人级的&#xff0c;好像企业级的不会这样,仅仅是项目需要做…

陆首群:评人工智能如何走向新阶段?

作者 | 陆首群&#xff0c;中国开源软件推进联盟名誉主席 出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09; 编者按&#xff1a;近来&#xff0c;业内关于深度学习算法的潜力是否已达天花板的争论陆续发出。有人认为&#xff0c;基于深度学习算法的应用还有深度开拓空…

Mysql INSERT、REPLACE、UPDATE的区别

用于操作数据库的SQL一般分为两种&#xff0c;一种是查询语句&#xff0c;也就是我们所说的SELECT语句&#xff0c;另外一种就是更新语句&#xff0c;也叫做数据操作语句。言外之意&#xff0c;就是对数据进行修改。在标准的SQL中有3个语句&#xff0c;它们是INSERT、UPDATE以及…

软件调试的艺术笔记:GDB

很久之前&#xff0c;在https://blog.csdn.net/fengbingchun/article/details/41413381中简单整理过gdb中常用的一些命令&#xff0c;不齐全&#xff0c;这里按照《软件调试的艺术》一书中关于gdb的介绍再做次整理。《软件调试的艺术》于2009年由人民邮电出版社出版。 1. 预备…

App Store 审核被拒整理

整理以前和现在遇到的审核被拒第一&#xff1a;2.2 DetailsWe discovered one or more bugs in your app when reviewed on iPhone running iOS 8.1.3 on both Wi-Fi and cellular networks. Specifically, we were not able to complete the In App Purchase. When tapped on …

只服这篇“神文”:基于老子哲学、相对论的超级人工智能模型

作者 | Anonymous authors译者 | TroyChang出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09;在此前我们为大家介绍 ICLR 2020 论文投稿情况时&#xff0c;提到了一篇“神作”在论文中作者们提出一个 ASI 概念&#xff08;Artificial Super Intelligence&am…

Navicat Premium使用教程【比较详细】

Navicat Premium使用教程简介&#xff1a;Navicat Premium是众所周知的数据库操作软件。本文比较详细。1、打开Navicat Premium&#xff0c;点击连接&#xff0c;选择MySQL&#xff0c;创建新连接。输入安装MySQL是的用户名和密码。点击确定。2、admin数据连接已经创建成功。下…

如何用Neo4j和Scikit-Learn做机器学习任务?| 附超详细分步教程

作者 | Mark Needham译者 | Tianyu、Shawnice编辑 | Jane出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09;图算法不是一个新兴技术领域&#xff0c;在开源库中已经有很多功能强大的算法实现。近两年&#xff0c;业内的学者与科学家都在积极探索可以弥补深度…

Docker在Ubuntu16.04和Windows10家庭版上安装操作步骤

之前在 https://blog.csdn.net/fengbingchun/article/details/109559500 中对Docker作了简单的介绍&#xff0c;这里介绍下Docker在Ubuntu16.04 x86_64 64位上和Windows10 x86_64 64位家庭版上的安装过程。 在Ubuntu上安装Docker(或Docker引擎)&#xff0c;Ubuntu必须是64位的…

iOS 不同机型屏幕适配

// .pch 文件中写 // 判断是iPhone机型 /** 4s 960 * 640* 5/5s 1136 x 640* 6/6s/7/8 4.7英寸 1334 x 750* 6p/6sp/7p/8p 5.5英寸 1920 x 1080* X 5.8英寸 2436 x 1125}*/ #define IS_IPHONE_4s [UIScreen instancesRespondToSelector:selector(currentMode)] ? \ CGSizeEqua…

北京中天荣泰视觉检测 仿真

www.romtek.cnhttp://jobs.zhaopin.com/191485013250433.htm?ssidkeyy&ff01&ss101转载于:https://www.cnblogs.com/pengkunfan/p/4316018.html

Docker客户端常用命令整理

之前在 https://blog.csdn.net/fengbingchun/article/details/109584460 中介绍过在Windows10家庭版和Ubuntu16.04上安装Docker的操作步骤&#xff0c;这里整理下Docker客户端常用命令。 在Windows10家庭版上运行Docker后&#xff0c;通过VMware就不能打开Ubuntu16.04虚拟机了…

深度学习入门笔记,三流程序员如何凭借实力逆袭高薪?你不服不行!

最近经常有朋友提及&#xff0c;想要入门深度学习&#xff0c;该如何学习&#xff1f;关于深度学习&#xff0c;网上的资料很多&#xff0c;不过貌似大部分都不太适合初学者。 我曾经是一名三流程序员&#xff0c;每天的工作内容就是在前人留下的 bug 上写新的bug&#xff0c;我…

Swift编程语言

The Swift Programming Language中文手册1.【精校版】The Swift Programming Language--欢迎使用Swift--关于Swift2.【精校版】The Swift Programming Language-欢迎使用Swift-Swift 初见3.The Swift Programming Language--语言指南--基础部分4.The Swift Programming Languag…

ondblog 修改informix日志模式

-N No Logging 没有日志-U Unbuffered Logging 非缓冲日志-B Buffered Logging 缓冲日志-A Unbuffered Logging, Mode ANSI ANSI模式No Logging 没有日志----“没有日志”模式只向逻辑日志写很少的信息&#xff0c;它只记录执行的DDL语句&#xff0c;这些语句影响到的行并…

iOS RunLoop详解

一、简介 CFRunLoopRef源码RunLoop是一个对象&#xff0c;这个对象在循环中用来处理程序运行过程中出现的各种事件&#xff08;比如说触摸事件、UI刷新事件、定时器事件、Selector事件&#xff09;&#xff0c;从而保持程序的持续运行&#xff1b;而且在没有事件处理的时候&…

开源库jemalloc简介

jemalloc是通用的malloc(3)实现&#xff0c;它强调避免碎片和可扩展的并发支持。它的源码位于https://github.com/jemalloc/jemalloc&#xff0c;最新稳定版本为5.2.1。 glibc的内存分配算法是基于dlmalloc实现的ptmalloc&#xff1b;tcmalloc是Google开发的内存分配器&#x…

改善深度学习训练的trick总结 | CSDN博文精选

扫码参与CSDN“原力计划”作者 | ZesenChen来源 | CSDN博客精选在深度学习中&#xff0c;同样一个模型用不同的初始化&#xff0c;数据处理&#xff0c;batch size&#xff0c;学习率&#xff0c;优化器都能得到不同性能的参数。我根据自己参与过的比赛中经常用到的一些trick进…

jQuery中的Ajax----03

为什么80%的码农都做不了架构师&#xff1f;>>> $.ajax(0方式是jQuery最底层的Ajax实现。 它的结构为: $.ajax(options) 该方法只有1个参数&#xff0c;但在这个对象里包含了$.ajax()方法所需要的请求设置以及回调函数等信息。参数以key/value的形式存在&#xff0…

Docker容器中数据两种持久化存储方式:卷和挂载宿主目录

镜像使用的是分层存储&#xff0c;容器也是如此。每一个容器运行时&#xff0c;是以镜像为基础层&#xff0c;在其上创建一个当前容器的存储层&#xff0c;我们可以称这个为容器运行时读写而准备的存储层为容器存储层。容器存储层的生存周期和容器一样&#xff0c;容器消亡时&a…

CFRunLoopRef 的内部逻辑(向 ibireme学习)

据苹果在文档里的说明&#xff0c;RunLoop 内部的逻辑大致如下:/// 用DefaultMode启动 void CFRunLoopRun(void) {CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); }/// 用指定的Mode启动&#xff0c;允许设置RunLoop超时时间 int CFRunL…

倒计时 3 天!「2019 嵌入式智能国际大会」全日程大公开!

立即抢购&#xff1a;https://t.csdnimg.cn/otBk还有3天&#xff0c;大伙期待的「2019嵌入式智能国际大会」正式开幕了&#xff01;2019年12月6日-7日&#xff0c;我们在深圳市人才研修院见&#xff01;大会以“万物互联泛在智能”为主题&#xff0c;邀请30位海内外顶级专家作为…