导航:首页 > 编程语言 > 汇编程序例子

汇编程序例子

发布时间:2023-03-24 01:19:40

❶ 写出一个汇编语言的框架程序,内容2个段,数据段和代码段

呵呵,建议你下了解一下杀毒软件的工作机制。
金山,360,瑞星等杀毒软件,都是驱动保护的,bat因为比它高层,所以做不到关闭杀毒软件。否则连批处理都可轻易攻破,那早就有无数病毒泛滥了。只有汇编语言这样的底层语言才可以,(其实C语言也能做到)。当然,bat也不是不行,可以用bat汇编,叫做ASCIICoding技术,挺难的。2007年以前国内还没有一个会的(除非会但不说,那我当然就不知道了)。也建议你学习Rootkit Hook技术,可以读一读这篇文章。

瘟神的尾行--Rootkit技术发展史
作者:小金
一. 无法驱逐的“助手”
网管小张正在手忙脚乱的寻找他的手工杀毒工具包,因为他在安装一个网管工具的时候无意中走了神,点击了“下一步”按钮后才惊觉安装程序的界面里一个不引人注目的角落里写着“安装CNNIC网络实名”这一行小字,而且最开头部分有一个小小的勾。于是着名的“中国网民的得力助手”便理所当然的在他的机器里安了家。
心里把厂商骂了十八遍的小张终于翻出了他外出修机时最得意的工具IceSword和超级巡警,果然在进程列表和SSDT列表里发现了红色警报,小张笑了笑,对付这些一般用户无法卸载的恶意流氓,自己可谓经验丰富了,当下便三下五除二的把CNNIC的进程给终结了,SSDT也给恢复了初始状态,然后小张去删除注册表启动项——突然发出的一个错误提示声音把小张吓了一跳,再定睛一看,他的笑容凝固了:“删除项时出错”。不会吧?小张急忙去删除CNNIC目录,结果彻底愣在了那里,系统弹出的错误提示很明确的告诉他,“无法删除文件,文件可能正在被使用”。怎么回事?小张一下子没了头绪……
达尔文的进化论告诉我们,“物竞天择,适者生存”,同样,在这安全与入侵的网络世界里,也在进行着这样一场选择的过程……

二. 被AIDS纠缠的互联网
。。。。。。。。。
。。。。。。。。。。。。。
。。。。。。。。。。。。。。(网络长度限制。。。想看全篇自己搜吧)
三. 结语

虽然到处都在提倡和谐网络的普及,但是,“健康上网”仅仅是指代那些黄赌毒而已吗?在利益面前,开发者的正义感越发渺小起来,我们的网络世界,是被瘟神紧紧跟随着的。技术的斗争越发激烈,但是用户的电脑知识是不会跟着时代发展而自动填充的,最终,大众上网的人民成了这一切技术较量的受害者。
这个荒谬的发展方向,何时才能休止呢?

还有这篇:

对大多数的Windows开发者来说,如何在Win32系统中对API函数的调用进行拦截一直是项极富挑战性的课题,因为这将是对你所掌握的计算机知识较为全面的考验,尤其是一些在如今使用RAD进行软件开发时并不常用的知识,这包括了操作系统原理、汇编语言甚至是机器指令(听上去真是有点恐怖,不过这是事实)。

当前广泛使用的Windows操作系统中,像Win 9x和Win NT/2K,都提供了一种比较稳健的机制来使得各个进程的内存地址空间之间是相互独立,也就是说一个进程中的某个有效的内存地址对另一个进程来说是无意义的,这种内存保护措施大大增加了系统的稳定性。不过,这也使得进行系统级的API拦截的工作的难度也大大加大了。

当然,我这里所指的是比较文雅的拦截方式,通过修改可执行文件在内存中的映像中有关代码,实现对API调用的动态拦截;而不是采用比较暴力的方式,直接对可执行文件的磁盘存储中机器代码进行改写。

二、API钩子系统一般框架
通常,我们把拦截API的调用的这个过程称为是安装一个API钩子(API Hook)。一个API钩子基本是由两个模块组成:一个是钩子服务器(Hook Server)模块,一般为EXE的形式;一个是钩子驱动器(Hook Driver)模块,一般为DLL的形式。

钩子服务器主要负责向目标进程注入钩子驱动器,使得钩子驱动器运行在目标进程的地址空间中,这是关键的第一步,而钩子驱动器则负责实际的API拦截处理工作,以便在我们所关心的API函数调用的之前或之后能做一些我们所希望的工作。一个比较常见的API钩子的例子就是一些实时翻译软件(像金山词霸)中必备的的功能:屏幕抓词。它主要是对一些Win32 API中的GDI函数进行了拦截,获取它们的输入参数中的字符串,然后在自己的窗口中显示出来。

针对上述关于API钩子的两个部分,有以下两点需要我们重点考虑的: 选用何种DLL注入技术,以及采用何种API拦截机制。

三、注入技术的选用
由于在Win32系统中各个进程的地址是互相独立的,因此我们无法在一个进程中对另一个进程的代码进行有效的修改,但如果你要完成API钩子的工作又必须如此。因此,我们必须采取某种独特的手段,使得API钩子(准确的说是钩子驱动器)能够成为目标进程中的一部分,才有较大的可能来对目标进程数据和代码进行有控制的修改。

通常可采用的几种注入方式:

1.利用注册表
如果我们准备拦截的进程连接了User32.dll,也就是使用了 User32.dll中的API(一般图形界面的应用程序都是符合这个条件),那么就可以简单把你的钩子驱动器DLL的名字作为值添加在下面注册表的键下: HKEY_LOCAL_MACHINE\Software\Microsoft\WindowsNT\CurrentVersion\Windows\AppInit_DLLs 值的形式可以为单个DLL的文件名,或者是一组DLL的文件名,相邻的名称之间用逗号或空格间隔。所有由该值标识的DLL将在符合条件的应用程序启动的时候装载。这是一个操作系统内建的机制,相对其他方式来说危险性较小,但它也有一些比较明显的缺点:该方法仅适用于NT/2K操作系统,显然看看键的名称就可以明白;如果需要激活或停止钩子的注入,只有重新启动Windows,这个就似乎太不方便了;最后一点也很显然,不能用此方法向没有使用User32.dll的应用程序注入DLL,例如控制台应用程序等。另外,不管是否为你所希望,钩子DLL将注入每一个GUI应用程序,这将导致整个系统性能的下降!

2.建立系统范围的Windows钩子
要向某个进程注入DLL,一个十分普遍也是比较简单的方法就是建立在标准的Windows钩子的基础上。Windows钩子一般是在DLL中实现的,这是一个全局性的Windows钩子的基本要求,这也很符合我们的需要。当我们成功地调用SetWindowsHookEx函数之后,便在系统中安装了某种类型的消息钩子,这个钩子可以是针对某个进程,也可以是针对系统中的所有进程。一旦某个进程中产生了该类型的消息,操作系统会自动把该钩子所在的DLL映像到该进程的地址空间中,从而使得消息回调函数(在 SetWindowsHookEx的参数中指定)能够对此消息进行适当的处理,在这里,我们所感兴趣的当然不是对消息进行什么处理,因此在消息回调函数中只需把消息钩子向后传递就可以了,但是我们所需的DLL已经成功地注入了目标进程的地址空间,从而可以完成后续工作。

我们知道,不同的进程之间是不能直接共享数据的,因为它们活动在不同的地址空间中。但在Windows钩子 DLL中,有一些数据,例如Windows钩子句柄HHook,这是由SetWindowsHookEx函数返回值得到的,并且作为参数将在 CallNextHookEx函数和UnhookWindoesHookEx函数中使用,显然使用SetWindowsHookEx函数的进程和使用 CallNextHookEx函数的进程一般不会是同一个进程,因此我们必须能够使句柄在所有的地址空间中都是有效的有意义的,也就是说,它的值必须必须在这些钩子DLL所挂钩的进程之间是共享的。为了达到这个目的,我们就应该把它存储在一个共享的数据区域中。

在VC++中我们可以采用预编译指令#pragma data_seg在DLL文件中创建一个新的段,并且在DEF文件中把该段的属性设置为"shared",这样就建立了一个共享数据段。对于使用 Delphi的人来说就没有这么幸运了:没有类似的比较简单的方法(或许是有的,但我没有找到)。不过我们还是可以利用内存映像技术来申请使用一块各进程可以共享的内存区域,主要是利用了CreateFileMapping和MapViewOfFile这两个函数,这倒是一个通用的方法,适合所有的开发语言,只要它能直接或间接的使用Windows的API。

在Borland的BCB中有一个指令#pragma codeseg与VC++中的#pragma data_seg指令有点类似,应该也能起到一样的作用,但我试了一下,没有没有效果,而且BCB的联机帮助中对此也提到的不多,不知怎样才能正确的使用(或许是另外一个指令,呵呵)。

一旦钩子DLL加载进入目标进程的地址空间后,在我们调用UnHookWindowsHookEx函数之前是无法使它停止工作的,除非目标进程关闭。

这种DLL注入方式有两个优点: 这种机制在Win 9x/Me和Win NT/2K中都是得到支持的,预计在以后的版本中也将得到支持;钩子DLL可以在不需要的时候,可由我们主动的调用 UnHookWindowsHookEx来卸载,比起使用注册表的机制来说方便了许多。尽管这是一种相当简洁明了的方法,但它也有一些显而易见的缺点:首先值得我们注意的是,Windows钩子将会降低整个系统的性能,因为它额外增加了系统在消息处理方面的时间;其次,只有当目标进程准备接受某种消息时,钩子所在的DLL才会被系统映射到该进程的地址空间中,钩子才能真正开始发挥作用,因此如果我们要对某些进程的整个生命周期内的API调用情况进行监控,用这种方法显然会遗漏某些API的调用 。

3.使用 CreateRemoteThread函数
在我看来这是一个相当棒的方法,然而不幸的是,CreateRemoteThread这个函数只能在Win NT/2K系统中才得到支持,虽然在Win 9x中这个API也能被安全的调用而不出错,但它除了返回一个空值之外什么也不做。该注入过程也十分简单:我们知道,任何一个进程都可以使用 LoadLibrary来动态地加载一个DLL。但问题是,我们如何让目标进程(可能正在运行中)在我们的控制下来加载我们的钩子DLL(也就是钩子驱动器)呢?有一个API函数CreateRemoteThread,通过它可在一个进程中可建立并运行一个远程的线程--这个好像和注入没什么关系嘛?往下看!

调用该API需要指定一个线程函数指针作为参数,该线程函数的原型如下: Function ThreadProc(lpParam: Pointer): DWORD,我们再来看一下LoadLibrary的函数原型: Function LoadLibrary(lpFileName: PChar): HMole。发现了吧!这两个函数原型几乎是一样的(其实返回值是否相同关系不大,因为我们是无法得到远程线程函数的返回值的),这种类似使得我们可以把直接把LoadLibrary当做线程函数来使用,从而在目标进程中加载钩子DLL。

与此类似,当我们需要卸载钩子DLL时,也可以FreeLibrary作为线程函数来使用,在目标进程中卸载钩子DLL,一切看来是十分的简洁方便。通过调用GetProcAddress函数,我们可以得到LoadLibrary函数的地址。由于 LoadLibrary是Kernel32中的函数,而这个系统DLL的映射地址对每一个进程来说都是相同的,因此LoadLibrary函数的地址也是如此。这点将确保我们能把该函数的地址作为一个有效的参数传递给CreateRemoteThread使用。 FreeLibrary也是一样的。

AddrOfLoadLibrary := GetProcAddress(GetMoleHandle(‘Kernel32.dll'), ‘LoadLibrary');

HRemoteThread := CreateRemoteThread(HTargetProcess, nil, 0, AddrOfLoadLibrary, HookDllName, 0, nil);

要使用CreateRemoteThread,我们需要目标进程的句柄作为参数。当我们用 OpenProcess函数来得到进程的句柄时,通常是希望对此进程有全权的存取操作,也就是以PROCESS_ALL_ACCESS为标志打开进程。但对于一些系统级的进程,直接这样显然是不行的,只能返回一个的空句柄(值为零)。为此,我们必须把自己设置为拥有调试级的特权,这样将具有最大的存取权限,从而使得我们能对这些系统级的进程也可以进行一些必要的操作。

4.通过BHO来注入DLL
有时,我们想要注入DLL的对象仅仅是Internet Explorer,很幸运,Windows操作系统为我们提供了一个简单的归档方法(这保证了它的可靠性!)―― 利用Browser Helper Objects(BHO)。一个BHO是一个在 DLL中实现的COM对象,它主要实现了一个IObjectWithSite接口,而每当IE运行时,它会自动加载所有实现了该接口的COM对象。

四、拦截机制
在钩子应用的系统级别方面,有两类API拦截的机制――内核级的拦截和用户级的拦截。内核级的钩子主要是通过一个内核模式的驱动程序来实现,显然它的功能应该最为强大,能捕捉到系统活动的任何细节,但难度也较大,不在本文的探讨范围之内(尤其对我这个使用Delphi的人来说,还没涉足这块领域,因此也无法探讨,呵呵)。

而用户级的钩子则通常是在普通的DLL中实现整个API的拦截工作,这才是此次重点关注的。拦截API函数的调用,一般可有以下几种方法:

1. 代理DLL(特洛伊木马
一个容易想到的可行的方法是用一个同名的DLL去替换原先那个输出我们准备拦截的API所在的DLL。当然代理DLL也要和原来的一样,输出所有函数。但如果想到DLL中可能输出了上百个函数,我们就应该明白这种方法的效率是不高的,估计是要累死人的。另外,我们还不得不考虑DLL的版本问题,很是麻烦。

2.改写执行代码
有许多拦截的方法是基于可执行代码的改写,其中一个就是改变在CALL指令中使用的函数地址,这种方法有些难度,也比较容易出错。它的基本思路是检索出在内存中所有你所要拦截的API的CALL指令,然后把原先的地址改成为你自己提供的函数的地址。

另外一种代码改写的方法的实现方法更为复杂,它的主要的实现步骤是先找到原先的API函数的地址,然后把该函数开始的几个字节用一个JMP指令代替(有时还不得不改用一个INT指令),从而使得对该API函数的调用能够转向我们自己的函数调用。实现这种方法要牵涉到一系列压栈和出栈这样的较底层的操作,显然对我们的汇编语言和操作系统底层方面的知识是一种考验。这个方法倒和很多文件型病毒的感染机制相类似。

3.以调试器的身份进行拦截
另一个可选的方法是在目标函数中安置一个调试断点,使得进程运行到此处就进入调试状态。然而这样一些问题也随之而来,其中较主要的是调试异常的产生将把进程中所有的线程都挂起。它也需要一个额外的调试模块来处理所有的异常,整个进程将一直在调试状态下运行,直至它运行结束。

4.改写PE文件的输入地址表
这种方法主要得益于现如今Windows系统中所使用的可执行文件(包括EXE文件和DLL文件)的良好结构――PE文件格式(Portable Executable File Format),因此它相当稳健,又简单易行。要理解这种方法是如何运作的,首先你得对PE文件格式有所理解。

一个PE文件的结构大致如下所示:一般PE文件一开始是一段DOS程序,当你的程序在不支持Windows的环境中运行时,它就会显示"This Program cannot be run in DOS mode"这样的警告语句;接着这个DOS文件头,就开始真正的PE文件内容了,首先是一段称为"IMAGE_NT_HEADER"的数据,其中是许多关于整个PE文件的消息,在这段数据的尾端是一个称为Data Directory的数据表,通过它能快速定位一些PE文件中段(section)的地址;在这段数据之后,则是一个"IMAGE_SECTION_HEADER"的列表,其中的每一项都详细描述了后面一个段的相关信息;接着它就是PE文件中最主要的段数据了,执行代码、数据和资源等等信息就分别存放在这些段中。

在所有的这些段里,有一个被称为".idata"的段(输入数据段)值得我们去注意,该段中包含着一些被称为输入地址表(IAT,Import Address Table)的数据列表,每个用隐式方式加载的API所在的DLL都有一个IAT与之对应,同时一个API的地址也与IAT中一项相对应。当一个应用程序加载到内存中后,针对每一个API函数调用,相应的产生如下的汇编指令:

JMP DWORD PTR [XXXXXXXX]

如果在VC++中使用了_delcspec(import),那么相应的指令就成为:

CALL DWORD PTR [XXXXXXXX]。

不管怎样,上述方括号中的总是一个地址,指向了输入地址表中一个项,是一个DWORD,而正是这个 DWORD才是API函数在内存中的真正地址。因此我们要想拦截一个API的调用,只要简单的把那个DWORD改为我们自己的函数的地址,那么所有关于这个API的调用将转到我们自己的函数中去,拦截工作也就宣告顺利的成功了。这里要注意的是,自定义的函数的调用约定应该是API的调用约定,也就是 stdcall,而Delphi中默认的调用约定是register,它们在参数的传递方法等方面存在着较大的区别。

另外,自定义的函数的参数形式一般来讲和原先的API函数是相同的,不过这也不是必须的,而且这样的话在有些时候也会出现一些问题,我在后面将会提到。因此要拦截API的调用,首先我们就要得到相应的IAT的地址。系统把一个进程模块加载到内存中,其实就是把 PE文件几乎是原封不动的映射到进程的地址空间中去,而模块句柄HMole实际上就是模块映像在内存中的地址,PE文件中一些数据项的地址,都是相对于这个地址的偏移量,因此被称为相对虚拟地址(RVA,Relative Virtual Address)。

于是我们就可以从HMole开始,经过一系列的地址偏移而得到IAT的地址。不过我这里有一个简单的方法,它使用了一个现有的API函数ImageDirectoryEntryToData,它帮助我们在定位IAT时能少走几步,省得把偏移地址弄错了,走上弯路。不过纯粹使用RVA从HMole开始来定位IAT的地址其实并不麻烦,而且这样还更有助于我们对PE文件的结构的了解。上面提到的那个API 函数是在DbgHelp.dll中输出的(这是从Win 2K才开始有的,在这之前是由ImageHlp.dll提供的),有关这个函数的详细介绍可参见MSDN。

在找到IAT之后,我们只需在其中遍历,找到我们需要的API地址,然后用我们自己的函数地址去覆盖它,下面给出一段对应的源码

procere RedirectApiCall; var ImportDesc:PIMAGE_IMPORT_DESCRIPTOR; FirstThunk:PIMAGE_THUNK_DATA32; sz:DWORD;
begin

//得到一个输入描述结构列表的首地址,每个DLL都对应一个这样的结构 ImportDesc:=ImageDirectoryEntryToData(Pointer(HTargetMole), true, IMAGE_DIRECTORY_ENTRY_IMPORT, sz);

while Pointer(ImportDesc.Name)<>nil do
begin //判断是否是所需的DLL输入描述

if StrIComp(PChar(DllName),PChar(HTargetMole+ImportDesc.Name))=0 then begin

//得到IAT的首地址
FirstThunk:=PIMAGE_THUNK_DATA32(HTargetMole+ImportDesc.FirstThunk);

while FirstThunk.Func<>nil do
begin

if FirstThunk.Func=OldAddressOfAPI then
begin

//找到了匹配的API地址 ......
//改写API的地址

break;

end;

Inc(FirstThunk);

end;

end;

Inc(ImportDesc);

end;

end;

最后有一点要指出,如果我们手工执行钩子DLL的退出目标进程,那么在退出前应该把函数调用地址改回原先的地址,也就是API的真正地址,因为一旦你的DLL退出了,改写的新的地址将指向一个毫无意义的内存区域,如果此时目标进程再使用这个函数显然会出现一个非法操作。

五、替换函数的编写
前面关键的两步做完了,一个API钩子基本上也就完成了。不过还有一些相关的东西需要我们研究一番的,包括怎样做一个替换函数。 下面是一个做替换函数的步骤: 首先,不失一般性,我们先假设有这样的一个API函数,它的原型如下:

function SomeAPI(param1: Pchar;param2: Integer): DWORD;

接着再建立一个与之有相同参数和返回值的函数类型:

type FuncType= function (param1: Pchar;param2: Integer): DWORD;

然后我们把SomeAPI函数的地址存放在OldAddress指针中。接着我们就可以着手写替换函数的代码了:

function DummyFunc(param1: Pchar;param2: Integer): DWORD; begin ......

//做一些调用前的操作

//调用被替换的函数,当然也可以不调用
result := FuncType(OldAddress) (param1 , param2);

//做一些调用后的操作

end;

我们再把这个函数的地址保存到NewAddress中,接着用这地址覆盖掉原先API的地址。这样当目标进程调用该API的时候,实际上是调用了我们自己的函数,在其中我们可以做一些操作,然后在调用原先的API函数,结果就像什么也没发生过一样。当然,我们也可以改变输入参数的值,甚至是屏蔽调这个API函数的调用。

尽管上述方法是可行的,但有一个明显的不足:这种替换函数的制作方法不具有通用性,只能针对少量的函数。如果只有几个API要拦截,那么只需照上述说的重复做几次就行了。但如果有各种各样的API要处理,它们的参数个数和类型以及返回值的类型是各不相同的,仍然采用这种方法就太没效率了。

的确是的,上面给出的只是一个最简单最容易想到的方法,只是一个替换函数的基本构架。正如我前面所提到的,替换函数的与原先的API函数的参数类型不必相同,一般的我们可以设计一个没有调用参数也没有返回值的函数,通过一定的技巧,使它能适应各种各样的API 函数调用,不过这得要求你对汇编语言有一定的了解。

首先,我们来看一下执行到一个函数体内前的系统堆栈情况(这里函数的调用方式为stdcall),函数的调用参数是按照从右到左的顺序压入堆栈的(堆栈是由高端向低端发展的),同时还压入了一个函数返回地址。在进入函数之前,ESP正指向返回地址。因此,我们只要从ESP+4开始就可以取得这个函数的调用参数了,每取一个参数递增4。另外,当从函数中返回时,一般在EAX中存放函数的返回值。

了解了上述知识,我们就可以设计如下的一个比较通用的替换函数,它利用了Delphi的内嵌式汇编语言的特性。

Procere DummyFunc;

asm add esp,4 mov eax,esp//得到第一个参数

mov eax,esp+4//得到第二个参数 ......

//做一些处理,这里要保证esp在这之后恢复原样

call OldAddress //调用原先的API函数 ......

//做一些其它的事情

end;

当然,这个替换函数还是比较简单的,你可以在其中调用一些纯粹用OP语言写的函数或过程,去完成一些更复杂的操作(要是都用汇编来完成,那可得把你忙死了),不过应该把这些函数的调用方式统一设置为stdcall方式,这使它们只利用堆栈来传递参数,因此你也只需时刻掌握好堆栈的变化情况就行了。如果你直接把上述汇编代码所对应的机器指令存放在一个字节数组中,然后把数组的地址当作函数地址来使用,效果是一样的。

六、后记
做一个API钩子的确是件不容易的事情,尤其对我这个使用Delphi的人来说,为了解决某个问题,经常在OP、C++和汇编语言的资料中东查西找,在程序调试中还不时的发生一些意想不到的事情,弄的自己是手忙脚乱。不过,好歹总算做出了一个 API钩子的雏形,还是令自己十分的高兴,对计算机系统方面的知识也掌握了不少,受益非浅。当初在写这篇文章之前,我只是想翻译一篇从网上Down下来的英文资料(网址为 ,文章名叫"API Hook Revealed",示例源代码是用VC++写的,这里不得不佩服老外的水平,文章写得很有深度,而且每个细节都讲的十分详细)。

❷ 汇编语言是怎么调用c语言的程序的

一、 参数传递的基本规则(ATPCS(ARM—Thumb Procere Call Standard))
1、 参数传递

二、汇编程序、C程序相互调用举例
1、 C程序调用汇编程序
汇编程序的设计要遵守ATPCS(ARM—Thumb Procere Call Standard),保证程序调用时参数的正确传递。在汇编程序中使用EXPORT 伪操作声明本程序,使得本程序可以被别的程序调用。在C程序使用extern声明该汇编程序。
下面是一个C程序调用汇编程序的例子。其中汇编程序str实现字符串复制功能,C程序调用str完成字符串复制的工作。
//C程序
#include <stdio.h>
extern void str(char *d, const char *s);
int main( )
{
const char *srcstr=”First string-source”;
char dststr[ ]=”Second string-destination”;
printf(“Before ing:\n”);
printf(“%s\n %s\n”, srcstr,dststr);
str(dststr,srcstr);
printf(“After ing:\n”);
printf(“%s\n %s\n “,srcstr,dststr);
while(1) ;
}

;汇编程序
AREA S, CODE, READONLY
EXPORT str
Str
LDRB R2, [R1], #1
STRB R2, [R0], #1
CMPR2,#0
BNE Str
MOV PC, LR
END
2、 汇编程序调用C程序
汇编程序的设计要遵守ATPCS,保证程序调用时参数的正确传递。在汇编程序中使用IMPORT伪操作声明将要调用的C程序。下面是一个汇编程序调用C程序的例子。其中在汇编程序中设置好各参数的值。本例中有6个参数,分别使用寄存器R0存放第1个参数,
R1存放第2个参数, R2存放第3个参数, R3存放第4个参数, 第5个、第6个参数利用数据栈传送。由于利用数据栈传递参数,在程序调用结束后要调整数据栈指针。
//C程序g( )返回6个参数的和
int g( int a, int b, int c, int d, int e, int f )
{
printf(“e=%d\n”, e);
printf(“f=%d\n”, f);
return (a+b+c+d+e+f);
}

; 汇编程序调用C程序 g( ) 计算6个整数 i, 2*i, 3*i, 4*i, 5*i, 6*i的和
EXPORT f
AREA f ,CODE, READONLY
IMPORT g
MOV R0, #1
ADD R1, R0, R0
ADD R2, R1, R0
ADD R3, R2, R0
ADD R4, R3, R0
ADD R5, R4, R0
STR R4, [SP, #-4]!
STR R5, [SP, #-4]!
BL g
ADD SP, SP, #4
ADD SP, SP, #4
STOP B STOP
END

❸ 汇编代码实例

伪 指 令
伪指令是对汇编起某种控制作用的特殊命令,其格式与通常的操作指令一样,并可加在汇编程序的任何地方,但它们并不产生机器指令。许多伪指令要求带参数,这在定义伪指令时由“表达式”域指出,任何数值与表达式匀可以作为参数。不同汇编程序允许的伪指令并不相同,以下所述的伪指令仅适用于MASM51系统,但一些基本的伪指令在大部份汇编指弊好程序中都能使用,当使用其它的汇编程序版本时,只要注意一下它们之间的区别就可以了。MASM51中可用的伪指令有:ORG 设置程序起始地址END 标志源代码结束EQU 定义常数SET 定义整型数DATA 给字节类型符号定值BYTE 给字节类型符号定值WROD 给字类唯铅型符号定值BIT 给位地址取名ALTNAME 用自定义名取代保留字DB 给一块连续的存储区装载字节型数据DW 给一块连续的存储区装载字型数据DS 预留一个连续的存储区或装入指定字节。INCLUDE 将一个源文件插入程序中TITLE 列表文件中加入标题行NOLIST 汇编时不产生列表文件NOCODE 条件汇编时,条件为假的不产生清单一、ORG 伪指令ORG用于为在它之后的程序设置地址值,它有一个参数,其格式为:ORG 表达式表达式可以是一个具体的数值,也可以包含变量名,如果包含变量名,则必须保证,当第一次遇到这条伪指令时,其中的变量必须已有定义(已有具体的数值),否则,无定义的值将由0替换,这将会造成错误。在列表文件中,由ORG定义的指令地址会被打印出来。ORG指令有什么用途呢?指令被翻译成机器码后,将被存入系统的ROM中,一般情况下,机器码总是一个接一个地放在存储器中,但有一些代码,其位置有特殊要求,典型的是五个中断入口,它们必须被放在0003H,000BH,0013H,001BH和0023H的位置,否则就会出错,如果我们编程时不作特殊处理,让机器代码一个接一个地生成,不能保证这些代码正好处于这些规定的位置,执行就会出错,这时就要用到ORG伪指令了。看如下例子:例:INT_0 EQU 1000HTIME_0 EQU 1010HINT_1 EQU 1020HTIME_1 EQU 1030HSERIAL EQU 1040HAJMP START ;跳转到主程序起始点LJMP INT_0 ;外中断0处理程序LJMP TIME_0 ;定时中断0处理程序LJMP INT_1 ;外中断1处理卜行程序LJMP TIME_1 ;定时中断1处理程序LJMP SERIAL ;串行口中断程序START:NOPEND上面的程序经汇编后列表文件如下:The Cybernetic Micro Systems 8051 Family Assembler, Version 3.03 Page 108-26-96 1000 = INT_0 EQU 1000H1010 = TIME_0 EQU 1010H1020 = INT_1 EQU 1020H1030 = TIME_1 EQU 1030H1040 = SERIAL EQU 1040H0000 0111 AJMP START ;跳转到主程序起始点0002 021000 LJMP INT_0 ;外中断0处理程序0005 021010 LJMP TIME_0 ;定时中断0处理程序0008 021020 LJMP INT_1 ;外中断1处理程序000B 021030 LJMP TIME_1 ;定时中断1处理程序000E 021040 LJMP SERIAL ;串行口中断程序START:0011 00 NOP0000 ENDThe Cybernetic Micro Systems 8051 Family Assembler, Version 3.03 Page 208-26-96;%T Symbol Name Type ValueINT_0 . . . . . . . . . . . . . I 1000INT_1 . . . . . . . . . . . . . I 1020SERIAL. . . . . . . . . . . . . I 1040START . . . . . . . . . . . . . L 0011TIME_0. . . . . . . . . . . . . I 1010TIME_1. . . . . . . . . . . . . I 1030;%Z00 Errors (0000)由列表文件,可以绘出代码在ROM中的映象图如下:代码
01H
11H
02H
10H
00H
02H
10H
10H
02H
10H
20H

地址
00H
01H
02H
03H
04H
05H
06H
07H
08H
09H
0AH

代码
02H
10H
30H
02H
10H
40H
00H





地址
0BH
0CH
0DH
0EH
0FH
10H
11H
12H
13H
14H
15
由上面的映象图可知,在03H处的代码为10H,而不是我们要的02H,所以外断程序INT_0不能被正确执行,其它各中断程序的情况同样如此,如在0BH处,本来存放的应当是定时器0中断程序,但按上述的映象图,0BH处开始的3个代码是:02H,10H,30H,这是定时器1的入口地址,所以,如果定时器0发生中断,所执行的其实是定时器1的中断程序,这当然不对。例2:INT_0 EQU 1000HTIME_0 EQU 1010HINT_1 EQU 1020HTIME_1 EQU 1030HSERIAL EQU 1040HAJMP START ;跳转到主程序起始点ORG 0003HLJMP INT_0 ;外中断0处理程序ORG 000BHLJMP TIME_0 ;定时中断0处理程序ORG 0013HLJMP INT_1 ;外中断1处理程序ORG 001BHLJMP TIME_1 ;定时中断1处理程序ORG 0023HLJMP SERIAL ;串行口中断程序START:NOPEND上面的程序经过汇编后列表文件如下:The Cybernetic Micro Systems 8051 Family Assembler, Version 3.03 Page 108-26-961000 = INT_0 EQU 1000H1010 = TIME_0 EQU 1010H1020 = INT_1 EQU 1020H1030 = TIME_1 EQU 1030H1040 = SERIAL EQU 1040H0000 0126 AJMP START ;跳转到主程序起始点0003 ORG 0003H0003 021000 LJMP INT_0 ;外中断0处理程序000B ORG 000BH000B 021010 LJMP TIME_0 ;定时中断0处理程序0013 ORG 0013H0013 021020 LJMP INT_1 ;外中断1处理程序001B ORG 001BH001B 021030 LJMP TIME_1 ;定时中断1处理程序0023 ORG 0023H0023 021040 LJMP SERIAL ;串行口中断程序START:0026 00 NOP0000 ENDThe Cybernetic Micro Systems 8051 Family Assembler, Version 3.03 Page 208-26-96;%T Symbol Name Type ValueINT_0 . . . . . . . . . . . . . I 1000INT_1 . . . . . . . . . . . . . I 1020SERIAL. . . . . . . . . . . . . I 1040START . . . . . . . . . . . . . L 0026TIME_0. . . . . . . . . . . . . I 1010TIME_1. . . . . . . . . . . . . I 1030;%Z00 Errors (0000)由列表文件,可以绘出代码在ROM中的映象图如下:代码
01H
11H

02H
10H
00H






地址
00H
01H
02H
03H
04H
05H
06H
07H
08H
09H
0AH

代码
02H
10H
10H





02H
01H
20H

地址
0BH
0CH
0DH
0EH
0FH
10H
11H
12H
13H
14H
15H

代码





02H
10H
30H




地址
16H
17H
18H
19H
1AH
1BH
1CH
1DH
1EH
1FH
20H

代码


02H
10H
40H
00H






地址
21H
22H
23H
24H
25H
26H
27H
28H
29H
2AH
2BH
由映象图可知,各中断程序的代码都在其规定地址处,一旦产生中断即可执行相应的程序。至于图中未填的部分(如02H),根据各编程器不同而不同,一般为FFH或00H。 二、END END语句标志源代码的结束,汇编程序遇到END语句即停止运行。若没有END语句,汇编将报错。END语句有一个参数,可以是数值0,也可以是表达式,其格式是:标号: END 表达式它的值就是程序的地址并且作为一个特殊的记录写入HEX文件。若这个表达式省略,HEX文件中其值就是0。三、EQU EQU以及其它一些符号定义伪指令用来给程序中出现的一些符号赋值。对这些符号名的要求与其它符号相同,即长度不限,大小写字母可互换并且必须以字母开头。由等值指令定义的符号是汇编符号表的一部分。等值伪指令有两种形式。一种用EQU,另一种用字符“=”即符号名 EQU 表达式符号名 = 表达式两种形式的效果是一样的。符号名在左边,其对应的值在右边。值可以是变元,其它的符号名或表达式。只要在两遍扫描中求出表达式的值就行,否则引用该符号名时将报错。当表达式的值是字符串时,只取后两个字符。若串长为1,高位字节被置0,符号名的值被打印在程序清单中。由等值伪指令定义的符号名不允许重名。如果经定义的符号名被重定义,则汇编将报出错,并且这个符号名按新定义的处理,最好不要在程序中出现重名。例:0469= ABC EQU 469H0464= XY EQU ABC-502F0= JK = 7520754 XYJK = XY+JK在列表文件中最左边的数字不是这些伪指令所在的地址而是通过汇编后赋给符号名的值。第一条符号名ABC被起来469H,第二条XY被赋于ABC-5,因此XY的值为469H-5=464H,JK的值为752(即2F0H),XYJK的值XY+JK=464H+2F0H=754H四、SET SET伪指令有些类似于等值伪指令,它定义了一个整数类型的符号名,它的格式为符号名 SET 表达式SET伪指令与等值伪指令的唯一区别在于SET伪指令所定义的符号名右以在程序中多次定义,而不报错。例:002D= K57 SET 101101B8707= K57 SET 34567五、DATA与BYTE DATA与BYTE都是用来定义字节类型的存储单元,赋予字节类型的存储单元一个符号名,以便在程序中通过符号名来访问这个存储单元,以帮助对程序的理解。BYTE与DATE之间的区别类似于EQU和SET,BYTE伪指令不能定义重名。六、WORD WORD伪指令类似于DATE伪指令,只是WORD伪指令定义了一个字类型的符号名,其格式为:符号名 WORD 表达式0027= VAL31 WORD 390021= PAR7 WORD 21H一个字由2个字节组成。当然,因为8051汇编语言集没有字操作,所以程序执行时,只处理字节。WROD伪指令仅仅允许用户定义一个认为是字的存储位置。七、BIT BIT伪指令定义了一个字位类型的符号名,其格式为:符号名 BIT 表达式这里表达式的值是一个位地址,这个伪指令有助于位的地址符号化。例:002F= LOG3 BIT 470014= Y731 BIT 14H八、ALTNAME 替换名(ALTNAME)伪指令提供用户一种手段,以定义一个符号名来替换一个保留字,此后这个答名与被替换的保留字均可等效地用于程序中。任何保留类型的答名均可被替换。替换名伪指令格式为:ALTNAME 保留字,新名例:0002= ALTNAME R2 COUNT013A EA MOV A,R2013B E502 MOV A,COUNT九、DB DB伪指令用于定义一个连续的存储区,给该存储区的存储单元赋值。该伪指令的参数即为存储单元的值,在表达式中对变元个数没有限制,只要此条伪指令能容纳在源程序的一行内,其格式为:标号: DB 表达式只要表达式不是字符串,每一表达式值都被赋给一个字节。计算表达式值时按16位处理,但其结果只取低8位,若多个表达式出现在一个DB伪指令中,它们必须以逗号分开。表达式中有字符串时,以单引号“'”作分隔符,每个字符占一个字节,字符串不加改变地被存在各字节中,并不将小写字母转换成大写字母。例如:DB 00H 01H 03H 46HDB 'This is a demo!'十、DW DW为以字节为单元(十六位二进制)来给一个的存储区赋值,其格式为:标号: DW 表达式例如:0000 3035 D46B DW 12341,54379,10110100101110B0004 2D2E0006 4344 4243 DW 'ABCD','BC','A'000A 0041000C 2868 02E8 DW 456*375h,83+295h,'YZ',72h-4560010 595A FEAA十一、DS DS为定义存储内容的伪指令,用它定义一个存储区,并用指定的参数填满该存储区。DS伪指令包含两个变元,第一个变元定义了存储区的长度的字节数,在汇编时,汇编程序将跳过这些单元把其它指令汇编在这些字节之后,因此在使用DS伪指令时第一个变元不可活力第二个变元表示在这些单元中真入什么值,第二个变元可以活力活力时这些字节将不处理。下例中0173处有一条DS 9,则空出9个字节,下一第指令被汇编到017C处;在017C处空出1BH个单元,在这些字节中被27H所填充。DS指令的格式如下:标号: DS 表达式1,表达式2表达式1定义了存储区的长度(以字节为单位)。这个变元不能省略。表达式2是可选择的,它的值低8位用以填入所定义的存储区。若省略则这部分存储单元不处理。例:0000 04 INC A0001 DS 9000A 04 INC A000B DS 1BH,27H0026 04 INC A十二、INCLUDE INCLUDE伪指令用于链接源文件,即将一个源文件插入到另一个源文件中。它有一个参数,指出将要插入的文件名,该文件名中可包括驱动器名和路径名。若文件没有扩展名,则默认为是ASM。但待插入的文件必须是可以打开的。若文件打开操作失败,则产生致命错误,汇编将停止运行。反之,汇编程序将文件内容读入并按源代码处理。当遇到文件结速符时,汇编程序返回到INCLUDE伪指令处继续身下处理源程序。被插入的文件在程序清单中以“I”开头。本宏汇编版本支持级嵌套,可在程序中用INCLUDE伪指令插入任意多个文件,但是,在一般情况下DOS允许打开的文件数量是有限的,如果用户需要打开较多的文件,则必须在CONFIG.SYS文件中加入FILES=40或更多的值,若超过8级嵌套或打开的文件太多,则产生致命错误,汇编中止运行。INCLUDE伪指令提供了模块化程序设计手段,在汇编程序处理主程序时,模块被插入,尽管这不等价于链接和装配可重定位的目标模块,但它具有类似的功能,被插入的源文件中不应该包含END伪指令,否则,汇编就会提前停止运行,END伪指令只能出现在主程序中。此外,在主程序进行汇编前所有附加的源文件必须通过汇编,产生相应的HEX及LST文件,由于附加的文件没有END伪指令,因此,附加文件汇编时,汇编程序将显示:“没有结束语句”的错误,但并不影响与主程序的链接。下面是一个使用INCLUDE伪指令的例子,其主程序的源文件MAIN.ASM为:;MAIN.ASMORG 27HSTART:CLR AMOV R3,AINCLUDE MOD1INC R5INCLUDE MOD2.ASMDEC R3END START主程序为带有END伪指令的完整的源文件。程序中有两INCLUDE伪指令,分别将两附加的文件MOD1.ASM及MOD2.ASM链接到主程序中。以下是这两个文件。;MOD1.ASMMOV R2,#31HMOV R5,#18H;MOD2.ASMMOV R6,#47HANL A,#07HMOV R1,A注意MOD1.ASM及MOD2.ASM均没有END指令。在进行汇编时必须先对MOD1.ASM和MOD2.ASM进行汇编,然后在汇编MAIN.ASM,由于上两个文件没有END伪指令,所以在汇编时会出现错误提示,不用管它,继续下面的工作,就可以得到正确的结果。以下是形成的列表文件:The Cybernetic Micro Systems 8051 Family Assembler, Version 3.03 Page 1;MOD2.ASMMOV R6,#47HANL A,#07HMOV R1,A08-27-96;MAIN.ASM0027 ORG 27HSTART:0027 E4 CLR A0028 FB MOV R3,AI INCLUDE MOD1I ;MOD1.ASMI0029 7A31 MOV R2,#31HI002B 7D18 MOV R5,#18HI002D 0D INC R5I INCLUDE MOD2.ASMI ;MOD2.ASMI002E 7E47 MOV R6,#47HI0030 5407 ANL A,#07HI0032 F9 MOV R1,A0033 1B DEC R30027 END STARTThe Cybernetic Micro Systems 8051 Family Assembler, Version 3.03 Page 208-27-96;%T Symbol Name Type ValueSTART . . . . . . . . . . . . . L 0027;%Z00 Errors (0000)十三、TITL TITLE伪指令用于在列表文件页头建立一个标题,其格式为:$TITLE 标题行这里标量行就是将出现在页头的标量与通常的字符串定义不同。这里标量行不加引号。汇编从$TITLE之后的第一个可打印字符开始,到回车符之间的字符串作为标量标量的最大长度是60个字符,基标量行省略,则标题行为空行。若TITLE伪指令在一页,它说明的标量行包含在本页,否则,标题将出现在下页页头。十四、PAGE PAGE伪指令用于形成新的一中定义一面的行数。其格式为:$PAGE 表达式若表达 式缺省则开始新的一页,若有表达式,则每页行数重新定义。汇编开始时页长为66行。一页中除出页外,剩余55行用于打印源程序,这一格式适用于标准打印纸。如果变元值小于66,页内可打印的源代码行将相应减少。页长最小值为12。若小于12时,每页内除页上只打印一行源程序。页长变元是16位字节,因而每页最长可定义到65535行,这时分页打印变为连续打印,在屏幕显示程序清单或在卷筒纸上打印程序清单时,常常使用连续打印,如果在启动汇编时用/N选项,页长就是65535。十五、LIST与NOLIST 它们的格式为:$LIST$NOLISTLIST伪指令使汇编时主生程序清单,但即使不用该指令,汇编也会自动产生清单。但如果使用了NOLIST伪指令后需要继续主生清单则必须使用LIST伪指令。NOLIST伪指令使汇编时不产生清单,所有包含此伪指令及在这条伪指令之后的语句都不进入列表文件。当不需要任何列表文件,并且不需要显示程序清单时,可以在启动汇编时不加.L附加项,且在源代码的第一行加上NOLIST指令。使用NOLIST伪指令与附加项/L不同之处是NOLIST伪指令可加在源程序中,与LIST伪指令配合使用,使源程序中某些部分不产生清单。而不加附加项/L则不产生任何程序清单。不过,不管有无$NOLIST伪指令,程序在汇编时检查到的错误都将在屏幕上显示出错的源代码行及错误信息十六、NOCODE 其格式为 $NOCODENOCODE伪指令使得在汇编时,条件汇编程序结构中那些真值为假的条件不产生清单。有关条件汇编结构在下面介绍。如果没有这条伪指令,汇编将主生所有条件下的清单,不论其真值是否为真。但是假的条件,不产生目标码。而NOCODE伪指令使汇编清单中只列出那些由汇编程序用到的部分,因此,当使用NOCODE伪指令时,程序清单与源程序并非逐行对应。 本文来自CSDN博客,转载请标明出处: http://blog.csdn.net/sunnf/archive/2008/10/23/3129370.aspx

❹ 实现两个数相减的汇编语言程序

;80x86汇编程序例子:
CLS ;清进桐梁位(借位)标志
MOV BL,0AH ;被减数送BL
MOV CL,05H ;减数银局送锋轮让CL
SUB BL,CL ;两数相减

❺ 汇编语言程序

SRT:
MOV A,R7
MOV R5,A
SRT1:
CLR F0
MOV A,R5
DEC A
MOV R5,A
MOV R2,A
JZ SRT5
MOV A,R0
MOV R6,A
SRT2:
MOV A,@R0
MOV R3,A
INC R0
MOV A,@R0
MOV R4,A
CLR C
SUBB A,R3
JNC SRT4
SETB F0
MOV A,R3
MOV @R0,A
DEC R0
MOV A,R4
MOV @R0,A
INC R0
SRT4:
DJNZ R2,SRT2
MOV A,R6
MOV R0,A
JB F0,SRT1
SRT5:
RET
END
比如上面的这个例子,采用某种计逗或神算机山亏的汇编指令写出来的实现某个功能的程序团陵。
打字不易,如满意,望采纳。

❻ 简单汇编语言实例

(1)判断双字的高位字是不是1,如培销果不是,直接返回
(2)清除进位标志
(3)将低字求反,弊岩然后 +1
(4)将进位保存起来
(5)将高位字求反,加上刚配卜游才的进位
--------------
XXXX:
MOV AX, X + 1 ;取 X 的高位字
CMP AX, 32768
JB X_EXIT ;是正数就跳转

MOV AX, X ;取 X 的低位字
XOR AX, 0FFFFH ;取反
ADD AX, 1
PUSHF
MOV X, AX

MOV AX, X + 1 ;取X 的高位字
XOR AX, 0FFFFH ;取反
POPF
ADC AX, 0
MOV X + 1, AX

X_EXIT:
RET

阅读全文

与汇编程序例子相关的资料

热点内容
iphone13对wap3加密 浏览:553
pdf文件打开失败 浏览:911
dubbo怎么调用不同服务器接口 浏览:38
全能解压王app历史版本 浏览:73
优先队列与拓扑排序算法 浏览:279
pdf转换formacbook 浏览:869
pdf文件内容怎么编辑 浏览:46
134压缩机排气温度多少 浏览:254
unity等待编译后 浏览:804
黑鲨手机锁屏视频在哪个文件夹 浏览:779
wow地图解压后怎么压缩 浏览:819
有pdf却打不开 浏览:460
七星彩软件app怎么下载 浏览:217
32单片机的重映射哪里改 浏览:816
为什么前端不用刷算法题 浏览:708
对称加密系统和公钥加密系统 浏览:428
历史地理pdf 浏览:606
物联网云服务器框架 浏览:648
sybaseisql命令 浏览:183
android权威编程指南pdf 浏览:663