当前位置: 首页 > news >正文

《汇编语言:基于X86处理器》第5章 过程(1)

本章介绍过程,也称为子程序或函数。任何具有一定规模的程序都需要被划分为几个部分,其中某些部分要被使用多次。读者会发现寄存器可以传递参数,也将了解为了追踪过程的调用位置,CPU 使用的运行时堆栈。最后,本章会介绍本书提供的两个代码库,分别称为 Irvine 32 和 Irvine 64,其中包含了有用的工具来简化输入输出。

5.1 堆栈操作

如下图所示,如果把10个盘子垒起来,其结果就称为堆栈。虽然有可能从这个堆栈的中间移出一个盘子,但是,更普遍的是从顶端移除。新的盘子可以看加到堆栈顶部,但不能加在底部或中部(图5-1):

堆栈数据结构(stackdatastructure)的原理与盘子堆栈相同:新值添加到栈顶,删除值也在栈顶移除。通常,对各种编程应用来说,堆栈都是有用的结构,并且它们也容易用面向对象的编程方法来实现。如果读者已经学习过使用数据结构的编程课程,那么就应该已经用过堆栈抽象数据类型(stackabstractdatatype)堆栈也被称为LIFO结构(后进先出,Last-nFirst-Out),其原因是,最后进入堆栈的值也是第一个出堆栈的值。

本章将特别关注运行时堆栈(runtime stack)。它直接由 CPU 的硬件支持,是过程调用与返回机制的基本部分。大部分情况下,本章称它为堆栈。

5.1.1运行时堆栈(32位模式)

运行时堆栈是内存数组,CPU用ESP(扩展堆栈指针,extended stack pointer)寄存器对其进行直接管理,该寄存器被称为堆栈指针寄存器(stack pointer register)。32位模式下,ESP 寄存器存放的是堆栈中某个位置的32 位偏移量。ESP 基本上不会直接被程序员控制,反之,它是用 CALL、RET、PUSH 和 POP 等指令间接进行修改。

ESP总是指向添加,或压入(pushed)到偏移量栈顶的最后一个数值。为了便于说明,假设现有一个堆栈,内含一个数值。如图5-2所示ESP的内容是十六进制数00001000,即刚压人堆栈数值(00000006)的偏移量。在图中,当堆栈指针数值减少时,栈顶也随之下移。

上图中,每个堆栈位置都是32位长,这是32位模式下运行程序的情形。

这里讨论的运行时堆栈与数据结构课程中讨论的堆栈抽象数据类型(ADT,stackabstract data type)是不同的。运行时堆栈工作于系统层,处理子程序调用。堆栈 ADT是编程结构,通常用高级编程语言编写,如C++或Java。它用于实现基于后进先出操作的算法。

1.入栈操作

32位入栈操作把栈顶指针减4,再将数值复制到栈顶指针指向的堆栈位置。图5-3展示了把000000A5压人堆栈的结果,堆中已经有一个数值(00000006)。注意,ESP寄存器总是指向最后压人堆栈的数据项。图中显示的堆栈顺序与之前示例给出的盘堆栈顺序相反,这是因为运行时堆栈在内存中是向下生长的,即从高地址向低地址扩展。入栈之前ESP=00001000h;人栈之后,ESP=00000FFCh。图5-4显示了同一个堆总共压入4个整数之后的情况。

2.出栈操作

出栈操作从堆栈删除数据。数值弹出堆栈后,栈顶指针增加(按堆元素大小),指向堆栈中下一个最高位置。图5-5展示了数值00000002弹出前后的堆情况。

ESP 之下的堆栈域在逻辑上是空白的,当前程序下一次执行任何数值人栈操作指令都可以覆盖这个区域。

3.堆栈应用

运行时堆栈在程序中有一些重要用途:

●当寄存器用于多个目的时,堆栈可以作为寄存器的一个方便的临时保存区。在寄存器被修改后,还可以恢复其初始值。

●执行 CALL 指令时,CPU 在堆栈中保存当前过程的返回地址。

●调用过程时,输入数值也被称为参数,通过将其压入堆栈实现参数传递。

●堆栈也为过程局部变量提供了临时存储区域。

5.1.2 PUSH 和 POP 指令

1.PUSH 指令

PUSH 指令首先减少 ESP 的值,再将源操作数复制到堆栈。操作数是 16 位的,则 ESP减2,操作数是32位的,则ESP减4。PUSH指令有3 种格式:

PUSH reg mem16
PUSH rog/mem32
PUSH imm32

2.POP 指令

POP 指令首先把ESP指向的堆栈元素内容复制到一个16位或32 位目的操作数中,再增加 ESP 的值。如果操作数是16 位的,ESP 加 2,如果操作数是32 位的,ESP 加 4:

POP reg mem16
POP reg/mem32

3.PUSHFD和POPFD指令

PUSHFD 指令把 32 位 EFLAGS 寄存器内容压人堆栈,而 POPFD 指令则把栈顶单元内容弹出到EFLAGS 寄存器:

pushfd

popfd

不能用MOV指令把标识寄存器内容复制给一个变量,因此,PUSHFD 可能就是保存标志位的最佳途径。有些时候保存标志寄存器的副本是非常有用的,这样之后就可以恢复标志寄存器原来的值。通常会用 PUSHFD 和 POPFD 封闭一段代码:

pushfd ;保存标志寄存器

;任意语句序列

popfd ;恢复标志寄存器

当用这种方式使用人栈和出栈指令时,必须确保程序的执行路径不会跳过POPFD指令。当程序随着时间不断修改时,很难记住所有人栈和出栈指令的位置。因此,精确的文档就显得至关重要!

一种不容易出错的保存和恢复标识寄存器的方法是:将它们压入堆栈后,立即弹出给一个变量:

完整的测试代码:

;5.1.2_3.asm  .386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.data
saveFlags DWORD ?.code
main PROCpushfd					;标识寄存器内容入栈pop saveFlags			;复制给一个变量;下述语句从同一个变量中恢复标识寄存器内容:push saveFlags			;被保存的标识入栈popfd					;复制给标识寄存器INVOKE ExitProcess,0
main endp
end main

运行调试:

4.PUSHAD,PUSHA,POPAD和POPA

PUSHAD指令按照EAX、ECX、EDX、EBX、ESP(执行PUSHAD之前的值)、EBPESI 和 EDI 的顺序,将所有 32 位通用寄存器压入堆栈。POPAD 指令按照相反顺序将同样的寄存器弹出堆栈。与之相似,PUSHA指令按序(AX、CX、DX、BX、SP、BP、SI 和 DI)将16 位通用寄存器压入堆栈。POPA 指令按照相反顺序将同样的寄存器弹出堆栈。在 16 位模式下,只能使用PUSHA 和 POPA 指令。16位编程将在第14~17章中讨论。

如果编写的过程会修改32 位寄存器的值,则在过程开始时使用PUSHAD 指令,在结束时使用 POPAD 指令,以此保存和恢复寄存器的内容。示例如下列代码段所示:

MySub PROCpushad				;保存通用寄存器的内容..mov eax, ...mov edx, ...mov ecx, .....popad				;恢复通用寄存器的内容ret
MySub ENDP

必须要指出,上述示例有一个重要的例外:过程用一个或多个寄存器来返回结果时,不应使用PUSHA 和PUSHAD。假设下述ReadValue过程用EAX返回一个整数;调用 POPAD将会覆盖EAX中的返回值:

ReadValue PROCpushad						;保存通用寄存器的内容..mov eax, return_value..popad						;覆盖EAXret
ReadValue ENDP

PUSHAD 指令的全称是 "Push All Double (word)",即 “压入所有双字(32位)寄存器”

PUSHAD 用于在 32 位模式下将 8 个通用寄存器的值按特定顺序压入栈中,顺序如下:

  1. EAX
  2. ECX
  3. EDX
  4. EBX
  5. ESP(压入的是执行 PUSHAD 前的栈指针值)
  6. EBP
  7. ESI
  8. EDI

测试代码:

;5.1.2_4.asm  .386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.code
main PROCpushad						;压入所有双字(32位)寄存器popad						;恢复通用寄存器的内容INVOKE ExitProcess,0
main endp
end main

运行调试:

示例: 字符串反转

现在查看名为RevStr的程序:在一个字符串上循环,将每个字符压人堆栈,再把这些字符从堆栈中弹出(相反顺序),并保存回同一个字符串变量。由于堆栈是LIFO(后进先出)结构,字符串中的字母顺序就发生了翻转:

;字符串翻转 (RevStr.asm)

;RevStr.asm  字符串反转.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.data
aName BYTE "Abraham Lincoln",0
nameSize = ($-aName)-1.code
main PROCmov edi, OFFSET aName			            ;查看内存数据测试用;将名字压入堆栈mov ecx, nameSizemov esi, 0
L1:	movzx	eax,aName[esi]			                ;获取字符push eax									;压入堆栈inc esiloop L1;将名字按逆序弹出堆栈,并存入aName数组mov ecx, nameSizemov esi, 0
L2:	pop eax										    ;获取字符mov aName[esi], al				            ;存入字符串inc esiloop L2INVOKE ExitProcess,0
main endp
end main

运行调试:

字符串反转:

5.1.3 本节回顾

1.哪个寄存器(32位模式中)管理堆栈?

答:ESP寄存器

2.运行时堆栈与堆栈抽象数据类型有什么不同?

答:运行时堆栈是唯一由CPU直接管理的堆栈。比如,它保留了被调用过程的返回地址。运行时的栈是从高地址向低地址增长。

3.为什么堆栈被称为LIFO结构?

答:LIFO表示“后进先出”。因为栈的数据操作只能在栈顶操作,最后入栈的数值第一个出栈。

4.当一个32位数值压人堆栈时,ESP发生了什么变化?

答:ESP的值会减4

5.(真/假):过程中的局部变量是在堆栈中新建的。

答:真

6.(真/假):PUSH指令不能用立即数作操作数。

答:假

5.2 定义并使用过程

如果读者已经学过了高级编程语言,那么就会知道将程序分割为子过程(subroutine)是多么有用。一个复杂的问题常常要分解为相互独立的任务,这样才易于被理解、实现以及有效地测试。在汇编语言中,通常用术语过程(procedure)来指代子程序。在其他语言中,子程序也被称为方法或函数。

就面向对象编程而言,单个类中的函数或方法大致相当于封装在一个汇编语言模块中的过程和数据集合。汇编语言出现的时间远早于面向对象编程,因此它不具备面向对象编程中的形式化结构。汇编程序员必须在程序中实现自己的形式化结构。

5.2.1 PROC 伪指令

1.定义过程过程

可以非正式地定义为:由返回语句结束的已命名的语句块。过程用 PROC 和 ENDP伪指令来定义,并且必须为其分配一个名字(有效标识符)。到目前为止,所有编写的程序都包含了一个名为main 的过程,例如:

main PROC
;
main ENDP

当在程序启动过程之外创建一个过程时,就用RET 指令来结束它。RET 强制 CPU 返回到该过程被调用的位置:

sample PROC
...
ret 
sample ENDP

2.过程中的标号

默认情况下,标号只在其被定义的过程中可见。这个规则常常影响到跳转和循环指令。在下面的例子中,名为Destination的标号必须与JMP指令位于同一个过程中:

jmp Destination

解决这个限制的方法是定义全局标号,即在名字后面加双冒号(::):

Destination::

就程序设计而言,跳转或循环到当前过程之外不是个好主意。过程用自动方式返回并调整运行时堆栈。如果直接跳出一个过程,则运行时堆栈很容易被损坏。关于运行时堆栈的更多信息请参阅8.2节。

3.示例:三个整数求和

现在创建一个名为 SumOf 的过程计算三个 32 位整数之和。假设在过程调用之前,整数已经分配给 EAX、EBX 和 ECX。过程用 EAX 返回和数:

SumOf PROCadd eax, ebx add eax, ecxret
SumOf ENDP

4.过程说明

要培养的一个好习惯是为程序添加清晰可读的说明。下面是对放在每个过程开头的信息的一些建议:

●对过程实现的所有任务的描述。

●输入参数及其用法的列表,并将其命名为 Receives(接收)。如果输入参数对其数值有特殊要求,也要在这里列出来。

●对过程返回的所有数值的描述,并将其命名为 Returns(返回)。

●所有特殊要求的列表,这些要求被称为先决条件(preconditions),必须在过程被调用之前满足。列表命名为 Requires。例如,对一个画图形线条的过程来说,一个有用的先决条件是该视频显示适配器必须已经处于图形模式。

上述选择的描述性标号,如Receives、Returns和Requires,不是绝对的;其他有用的名字也常常被使用。

有了这些思想,现在对SumOf过程添加合适的说明:

;sumof
;计算3个32位整数之和并返回和数。
;接收:EAX、EBX和ECX为3个整数,可能是有符号数,也可能是无符号数,
;返回:EAX=和数
SumOf PROCadd eax, ebx add eax, ecxret
SumOf ENDP

用高级语言,如 C和 C++,编写的函数,通常用 AL返回8 位的值,用 AX 返回 16 位的值,用 EAX 返回 32 位的值。

5.2.2 CALL和RET指令

CALL 指令调用一个过程,指挥处理器从新的内存地址开始执行。过程使用 RET(从过程返回)指令将处理器转回到该过程被调用的程序点上。从物理上来说,CALL 指令将其返回地址压人堆栈,再把被调用过程的地址复制到指令指针寄存器。当过程准备返回时,它的RET 指令从堆栈把返回地址弹回到指令指针寄存器。32 位模式下,CPU 执行的指令由 EIP(指令指针寄存器)在内存中指出。16 位模式下,由 IP 指出指令。

调用和返回示例

假设在 main 过程中,CALL 指令位于偏移量为0000 0020 处。通常,这条指令需要 5 个字节的机器码,因此,下一条语句(本例中为一条MOV指令)就位于偏移量为0000 0025 处:

					main PROC
00000020			call MySub
00000025			mov eax,ebx

然后,假设MySub 过程中第一条可执行指令位于偏移量0000 0040处:

						MySub PROC
00000040				mov eax,edx..retMySub ENDP

当CALL 指令执行时(图 5-6 ),调用之后的地址(0000 0025)被压入堆栈,MySub 的地址加载到 EIP。执行 MySub 中的全部指令直到RET 指令。当执行 RET 指令时,ESP 指向的堆栈数值被弹出到EIP(图 5-7,步骤 1)。在步骤 2 中,ESP 的数值增加,从而指向堆栈中的前一个值(步骤2)。

5.2.3 过程调用嵌套

被调用过程在返回之前又调用了另一个过程时,就发生了过程调用嵌套。假设 main调用了过程Sub1。当Sub1执行时,它调用了过程Sub2。当Sub2执行时,它调用了过程Sub3。步骤如图5-8所示。

当执行Sub3 末尾的RET指令时,将stack[ESP](堆栈段首地址+ESP给出的偏移量)中的数值弹出到指令指针寄存器中,这使得执行转回到调用 Sub3 后面的指令。下图显示的是执行从Sub3 返回操作之前的堆栈:

返回之后,ESP指向栈顶下一个元素。当Sub2末尾的RET指令将要执行时,堆如下所示:

最后,执行Sub1的返回,stack[ESP]的内容弹出到指令指针寄存器,继续在main中执行:

显然,堆栈证明了它很适合于保存信息,包括过程调用嵌套。一般说来,堆栈结构用于程序需要按照特定顺序返回的情况。

5.2.4 向过程传递寄存器参数

如果编写的过程要执行一些标准操作,如整数数组求和,那么,在过程中包含对特定变量名的引用就不是一个好主意。如果这样做了,该过程就只能作用于一个数组。更好的方法是向过程传递数组的偏移量以及指定数组元素个数的整数。这些内容被称为参数(或输入参数)。在汇编语言中,经常用通用寄存器来传递参数。

在前面的章节中创建了一个简单的过程SumOf,计算EAX、EBX和ECX中的整数之和。在 main 调用 SumOf之前,将数值分配给EAX、EBX和ECX:

.data
theSum DWORD ?
.code
main PROCmov eax,10000h						;参数mov ebx,20000h						;参数mov ecx,30000h						;参数call Sumof							;EAX=(EAX+EBX+ECX)mov theSum,eax						;保存和数

在CALL 语句之后,选择了将EAX 中的和数复制给一个变量。

完整代码测试笔记:

;5.2.4.asm  5.2.4 向过程传递寄存器参数.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.data
theSum DWORD ?.code;sumof
;计算3个32位整数之和并返回和数。
;接收:EAX、EBX和ECX为3个整数,可能是有符号数,也可能是无符号数,
;返回:EAX=和数
SumOf PROCadd eax, ebx add eax, ecxret
SumOf ENDPmain PROCmov eax,10000h						;参数mov ebx,20000h						;参数mov ecx,30000h						;参数call Sumof							;EAX=(EAX+EBX+ECX)mov theSum,eax						;保存和数INVOKE ExitProcess,0
main endp
end main

运行调试:

当执行call Sumof指令后,esp偏移值-4,把call的下一条指令偏移值入栈,然后跳转到SumOf过程中

计算结果:

5.2.5示例:整数数组求和

程序员在C++或Java中编写过的非常常见的循环类型是计算整数数组之和。这在汇编语言中很容易实现,它可以被编码为按照尽可能快的方式来运行。比如,在循环内可以使用寄存器而非变量。

现在创建一个过程ArraySum,从一个调用程序接收两个参数:一个指向32位整数数组的指针,以及一个数组元素个数的计数器。该过程计算和数,并用 EAX 返回数组之和:

;--------------------------------------------------------
;ArraySum.asm   5.2.5 示例:整数数组求和
;计算32位整数数组元素之和。
;接收:ESI=数组偏移量
;		 ECX=数组元素的个数
;返回:EAX=数组元素之和
;--------------------------------------------------------
ArraySum PROCpush esi								;保存ESIpush ecx								;保存ECXmov eax,0								;设置和数为0
L1:	add eax,[esi]						        ;将每个整数与和数相加add esi,TYPE DWORD			            ;指向下一个整数loop L1									;按照数组大小重复pop ecx									;恢复ECX和ESIpop esi								ret										;和数在EAX中
ArraySum ENDP

这个过程没有特别指定数组名称和大小,它可以用于任何需要计算32 位整数数组之和的程序。只要有可能,编程者也应该编写具有灵活性和适应性的程序。

测试ArraySum过程

下面的程序通过传递一个32位整数数组的偏移量和长度来测试ArraySum 过程。调用ArraySum 之后,程序将过程的返回值保存在变量 theSum 中。

;测试ArraySum 过程 (TestArraySum.asm)

;TestArraySum.asm    测试ArraySum过程 
.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.data
array DWORD 10000h,20000h,30000h,40000h,50000h
theSum DWORD ?.code
main PROCmov esi, OFFSET array				;ESI指向数组mov ecx, LENGTHOF array			    ;ECX=数组计数器call ArraySum						;计算和数mov theSum, eax						;用EAX返回和数INVOKE ExitProcess,0
main ENDP
;--------------------------------------------------------
;ArraySum.asm   
;计算32位整数数组元素之和。
;接收:ESI=数组偏移量
;		 ECX=数组元素的个数
;返回:EAX=数组元素之和
;--------------------------------------------------------
ArraySum PROCpush esi							;保存ESIpush ecx							;保存ECXmov eax,0							;设置和数为0
L1:	add eax,[esi]						    ;将每个整数与和数相加add esi,TYPE DWORD			        ;指向下一个整数loop L1								;按照数组大小重复pop ecx								;恢复ECX和ESIpop esi								ret									;和数在EAX中
ArraySum ENDP
END main

运行调试:

5.2.6 保存和恢复寄存器

在 ArraySum 示例中,ECX 和 ESI 在过程开始时被压入堆栈,在过程结束时被弹出堆栈。这是大多数过程修改寄存器的典型操作。总是保存和恢复被过程修改的寄存器,将使得调用程序确保自己的寄存器值不会被覆盖。但是对用于返回数值的寄存器应该例外,通常是指EAX,不要将它们压人和弹出堆栈。

USES 运算符

USES运算符与PROC伪指令一起使用,让程序员列出在该过程中修改的所有寄存器名。USES 告诉汇编器做两件事情:第一,在过程开始时生成 PUSH 指令,将寄存器保存到堆栈;第二,在过程结束时生成POP指令,从堆栈恢复寄存器的值。USES 运算符紧跟在PROC 之后,其后是位于同一行上的寄存器列表,表项之间用空格符或制表符(不是逗号)分隔。

5.2.5节给出的ArraySum过程使用PUSH和POP指令来保存和恢复ESI 和 ECX。USES 运算符能够更加容易地实现同样的功能:

ArraySum PROC USES esi ecxmov eax,0					;设置和数为0
L1:	add eax,[esi]			    ;将每个整数与和数相加add esi,TYPE DWORD			;指向下一个整数loop L1						;按照数组大小重复ret							;和数在EAX中
ArraySum ENDP

完整代码测试笔记

;TestArraySumUSES.asm    测试USES运行符 
.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD.data
array DWORD 10000h,20000h,30000h,40000h,50000h
theSum DWORD ?.code
main PROCmov esi, OFFSET array				;ESI指向数组mov ecx, LENGTHOF array			    ;ECX=数组计数器call ArraySum						;计算和数mov theSum, eax						;用EAX返回和数INVOKE ExitProcess,0
main ENDP
;--------------------------------------------------------
;USES运行符使用  
;计算32位整数数组元素之和。
;接收:ESI=数组偏移量
;		 ECX=数组元素的个数
;返回:EAX=数组元素之和
;--------------------------------------------------------
ArraySum PROC USES esi ecxmov eax,0							;设置和数为0
L1:	add eax,[esi]						;将每个整数与和数相加add esi,TYPE DWORD			        ;指向下一个整数loop L1								;按照数组大小重复ret									;和数在EAX中
ArraySum ENDP
END main

运行调试:

汇编器生成的相应代码展示了使用 USES的效果:

ArraySum PROC
           push esi                                
           push ecx 
                          
           mov eax,0                                ;设置和数为0
L1:      add eax,[esi]                            ;将每个整数与和数相加
          add esi,TYPE DWORD            ;指向下一个整数
          loop L1                                     ;按照数组大小重复
          pop ecx                                
          pop esi   
                            
          ret                                
ArraySum ENDP

调试提示: 使用 Microsoft Visual Studio调试器可以查看由MASM 高级运算符和伪指令生成的隐藏机器指令。在调试窗口中右键点击,选择Go To Disassembly。该窗口显示程序源代码,以及由汇编器生成的隐藏机器指令。

例外 当过程利用寄存器(通常用EAX)返回数值时,保存使用寄存器的惯例就出现了-个重要的例外。在这种情况下,返回寄存器不能被压人和弹出堆栈。例如下述SumOf过程把 EAX 压人、弹出堆栈,就会丢失过程的返回值:

SumOf PROC					;三个整数之和push eax					;保存EAXadd eax, ebxadd eax, ecx			;计算 EAX、EBX和ECX之和pop eax						;和数丢失!ret
SumOf ENDP

5.2.7 本节回顾

1.(真/假):PROC伪指令标识过程的开始,ENDP 伪指令标识过程的结束。

答:真

2.(真/假):可以在现有过程中定义一个过程。

答:假

3.如果在过程中省略RET指令会发生什么情况?

答:不会回到原调用函数的下一条指令处。过程结束后将会继续执行,很可能进入另一个过程的开始,这种编程错误通常难以被检测到。

4.在建议的过程说明中,如何使用名称Receives 和Returns?

答:Receives表示过程被调用时向其传递的输入参数。Returns表示过程返回到其调用者时可能产生值。

5.(真/假):CALL指令把自身指令的偏移量压人堆栈。

答:假

6.(真/假):CALL指令把紧跟其后的指令的偏移量压人堆栈。

答:真

5.3 链接到外部库

如果编程者花时间的话,就可以用汇编语言编写出详细的输入输出代码。就好比自己从头开始搭建汽车,然后可以驾车出行一样。这个工作很有趣但也很耗时。在第 11 章,读者将有机会了解MS-Windows模式下如何处理输入输出。这是很大的乐趣,当看到那些有用的工具时,一个新的世界就展现在眼前。不过现在,在学习汇编语言基础时,输入输出应该是很容易的。5.3 节将说明如何从本书的链接库Irvine32.lib和Irvine64.obj中调用过程。完整的链接库源代码可以在本书作者网站(asmirvine.com)上获取。在计算机上安装时,应该将它安装在本书安装文件(通常命名为C:\Irvine)下的Examples\Libs32子文件夹中。

Irvine32链接库只能用于32位模式下运行的程序。它包含了链接到MS-Windows API的过程,生成输入输出。对 64 位应用程序来说,Irvine64 链接库的限制更多,它仅限于基本显示和字符串操作。

5.3.1 背景知识

链接库是一种文件,包含了已经汇编为机器代码的过程(子程序)。链接库开始时是一个或多个源文件,这些文件再被汇编为目标文件。目标文件插人到一个特殊格式文件,该文件由链接器工具识别。假设一个程序调用过程WriteString在控制台窗口显示一个字符串。该程序源代码必须包含PROTO 伪指令来标识WriteString过程:

WriteString proto

之后,CALL指令执行WriteString:

call WriteString

当程序进行汇编时,汇编器将不指定 CALL 指令的目标地址,它知道这个地址将由链接器指定。链接器在链接库中寻找 WriteString,并把库中适当的机器指令复制到程序的可执行文件中。同时,它把WriteString 的地址插入到CALL指令。如果被调用过程不在链接库中,链接器就发出错误信息,且不会生成可执行文件。

链接命令选项 链接器工具把一个程序的目标文件与一个或多个目标文件以及链接库组合在一起。比如,下述命令就将hello.obj与irvine32.lib和kernel32.lib库链接起来:

link hello.obj irvine32.1ib kerne132.1ib

32位程序链接 kernel32.lib文件是Microsoft Windows平台软件开发工具(SoftwareDevelopment Kit)的一部分,它包含了kernel32.dll 文件中系统函数的链接信息。kernel32dl 文件是MS-Windows的一个基本组成部分,被称为动态链接库(dynamic link library)它含有的可执行函数实现基于字符的输入输出。图5-9展示了为什么 kernel32.lib是通向 kernel32.dll的桥梁。

在第1章到第10章中,程序都链接到Irvine32.1ib或者Irvine64.obj。第11章说明了如何将程序直接链接到kernel32.lib.

5.3.2 本节回顾

1.(真/假):链接库由汇编语言源代码组成。

答:假(其中包含了目标代码)

2.在一个外部链接库中,用PROTO伪指令声明过程 MyProc。

答:代码示例:MyProc PROTO

3.编写CALL语句调用外部链接库中的过程 MyProc。

答:代码示例:CALL MyProc

4.本书支持的32位链接库的名称是什么?

答:irvine32.lib

5.kernel32.dll是什么类型的文件?

答:Kernel32.dll为动态链接库,是MS-Windows操作系统的基本组成部分。

http://www.lryc.cn/news/575507.html

相关文章:

  • DSP学习笔记1
  • ISP Pipeline(4): Anti Aliasing Noise Filter 抗锯齿与降噪滤波器
  • 6月份最新代发考试战报:思科华为HCIP HCSE 考试通过
  • Spring 框架
  • Pytest项目_day03(Postman使用)
  • Servlet继承结构
  • [附源码+数据库+毕业论文]基于Spring+MyBatis+MySQL+Maven+vue实现的考研图书电子商务平台管理系统,推荐!
  • Docker 入门教程(二):Docker 的基本原理
  • Java基础知识(十一)GUI图形用户界面设计
  • iOS App 上架流程工具链解析:开发者视角下的协作实践总结
  • ubuntu 远程桌面 xrdp + frp
  • AI in CSR Writing: Revolutionizing Clinical Trial Reports
  • 专题:2025中国游戏科技发展研究报告|附130+份报告PDF、原数据表汇总下载
  • Linux命令与脚本:高效系统管理的双刃剑
  • 记dwz(JUI)前端框架使用之--服务端响应提示框
  • Llama 3 + Qwen2双模型实战:单张3090构建企业级多模态知识库(2025精解版)
  • MyBatis深度面试指南
  • 【PX4-AutoPilot教程-TIPS】PX4系统命令行控制台ConsolesShells常用命令(持续更新)
  • 2025Q1东南亚移动游戏:休闲游戏主导下载,本地化是出海重要战略!
  • Unified、Remark 和Rehype 是 JavaScript 生态中用于处理结构化文本(如 Markdown 和 HTML)的核心工具
  • UDP 和 TCP 可以同时使用相同的端口号
  • 创客匠人解析视频号公私域互通逻辑:知识变现的破圈与沉淀之道
  • Vue-15-前端框架Vue之应用基础编程式路由导航
  • MR30分布式IO:产线改造省时 70%
  • 七天学会SpringCloud分布式微服务——03——一些细节的心得感悟(续)
  • FANUC机器人教程:用户坐标系标定及其使用方法
  • 腾讯混元API调用优化实战:用API网关实现流量控制+缓存+监控
  • 向量数据库milvus中文全文检索取不到数据的处理办法
  • SQL学习笔记3
  • recipes的版本比较老如何更新到新版本?