中文版-Plan9汇编器手册-A Manual for the Plan 9 assembler

A Manual for the Plan 9 assembler

英文原链接:A Quick Guide to Go’s Assembler - The Go Programming Language

汇编器手册:A Manual for the Plan 9 assembler

编译器手册:Plan 9 C Compilers

每个MIPS,SPARC,Intel 386,AMD64,Power PC和ARM都有一个汇编器。 68020汇编器2a(不再分发)是最古老的原型,并且在许多方面都是原型。 汇编程序实际上只是单个程序的变体:它们共享许多属性,例如指令操作数的从左到右分配顺序,以及诸如MOVE之类的宏指令合成,以隐藏机器的负载和存储结构的特殊性。 为了保持具体,本手册的第一部分专门针对68020。最后是对其他汇编程序之间差异的描述。

罗伯·派克(Rob Pike)撰写的文件“如何使用Plan 9 C编译器”是本手册的前提。

寄存器

汇编器中的所有预定义符号均为大写。数据寄存器为R0至R7;地址寄存器是A0到A7;浮点寄存器为F0至F7。

C编译器使用A6中的指针指向数据,从而可以更短地使用短地址。 A6的值是常数,必须在C程序初始化期间将其设置为外部定义符号a6base的地址。

在汇编器中定义了以下硬件寄存器;对于68020手册,其含义应该显而易见:CAAR,CACR,CCR,DFC,ISP,MSP,SFC,SR,USP和VBR。

汇编器还定义了一些操纵堆栈的伪寄存器:FP,SP和TOS。 FP是帧指针,因此0(FP)是第一个参数,4(FP)是第二个参数,依此类推。 SP是本地堆栈指针,其中保存了自动变量(SP仅是68020上的伪寄存器); 0(SP)是第一个自动值,以此类推。最后,TOS是堆栈顶部的寄存器,用于将参数推送到过程,保存临时值等。

汇编器和加载器会跟踪这些伪寄存器,因此无论A7指向硬件堆栈上的内容如何,上述语句都是正确的。名称A7指的是硬件堆栈指针,但要小心混合使用A7和上述与堆栈相关的伪寄存器,这会造成麻烦。还要注意,加载程序会观察PEA指令来更改SP,因此会在所有返回之前插入相应的弹出窗口。汇编器接受要附加到FP和SP用法的类似标签的名称,例如p + 0(FP),以帮助说明p是例程的第一个参数。该名称位于符号表中,但对程序结果没有任何意义。

引用数据

所有外部引用都必须相对于某些伪寄存器(PC(虚拟程序计数器)或SB(“静态基数”寄存器))进行。 PC计算指令,而不是数据字节。 例如,要跳转到第二条指令,即跳过一条指令,可以写一条

  BRA 2(PC)

标签也被允许,如

   BRA return

    NOP

return:

    RTS

使用标签时,没有(PC)注释。

伪寄存器SB是指程序的地址空间的开头。因此,对全局数据和过程的引用被写为SB的偏移量,如

  MOVL    $array(SB), TOS

将全局数组的地址压入堆栈,或者

  MOVL    array+4(SB), TOS

推送数组的第二个(4字节)元素。注意偏移量的使用;寻址模式的完整列表如下。同样,子例程调用必须使用SB:

 BSR exit(SB)

文件静态变量具有语法

local<>+4(SB))

<>将在加载时由唯一的整数填充。

程序启动时必须执行

 MOVL  $a6base(SB), A6

在访问任何全局数据之前。 (在诸如MIPS和SPARC之类的不能在单个指令中加载寄存器的机器上,常量是通过静态基址寄存器加载的。加载程序会识别初始化静态基址寄存器的代码,并对其进行特殊处理。但是,您必须小心, 当未设置静态基址寄存器时(例如在中断例程的早期),请勿在此类计算机上加载较大的常量。)

表达方式

表达主要是人们所期望的。 如果需要偏移量或常量,则允许使用带有一元运算符的主表达式。 括号中允许使用通用C常数表达式。

源文件的处理过程与C编译器完全相同,因此#define和#include可以正常工作。

寻址方式

所有汇编程序都共享简单的寻址模式。 出于完整性考虑,此处列出所有68020寻址模式的表格,因为该机器设置最丰富。 在该表中,o是一个偏移量,如果可以忽略零,则为d,而d是偏移量,它是介于-128和127之间的常数。 列出的许多模式具有相同的名称。 仔细检查格式将显示正在应用的默认值。 例如,不提供地址寄存器的索引模式就像使用零值寄存器一样工作。 对于“偏移”,请阅读“位移”。 对于“ .s”,请读取.L或.W之一,后跟* 1,* 2,* 4或* 8,以指示数据的大小和缩放比例。

img

放下数据

将数据放入指令流(例如中断向量)很容易:伪指令LONG和WORD(但不是BYTE)放置适当大小的单个参数的值,就好像它是一条指令:

LONG    $12345

将长12345(以10为底)放置在指令流中。 (在大多数计算机上,唯一的此类运算符是WORD,它占用32位的数量。386具有全部三个:LONG,WORD和BYTE。AMD64将QUAD添加到64位值的QUAD中。960仅具有一个LONG。)

在数据部分中放置信息会更加痛苦。伪指令DATA提供了两个参数来完成这项工作:放置条目的地址(包括其大小)和放置在那里的值。例如,要定义一个包含字符abc和终止null的字符数组的数组:

  DATA    array+0(SB)/1,$’a’

  DATA    array+1(SB)/1,$’b’

  DATA    array+2(SB)/1,$’c’

  GLOBL   array(SB),$4

要么

  DATA    array+0(SB)/4,$"abc\z"

  GLOBL   array(SB),$4

/ 1定义要定义的字节数,GLOBL使符号成为全局符号,而$ 4表示符号占用多少字节。未初始化的数据将自动归零。字符\ z等效于C \ 0。 DATA语句中的字符串最多可包含八个字节。分段构建更大的字符串。 DYNT和INIT这两个伪指令允许(过时的)Alef编译器在加载阶段构建动态类型信息。 DYNT伪指令具有两种形式:

  DYNT    ,ALEF_SI_5+0(SB)

  DYNT    ALEF_AS+0(SB),ALEF_SI_5+0(SB)

在第一种形式中,DYNT将符号定义为一个小的唯一整数常量,由加载程序选择,该常量是字长的大约倍数。

在第二种形式中,DYNT以相同的方式定义第二个符号,将第一个符号指定的数组中最近定义的文本符号的地址放置在第二个符号的值定义的索引处,然后调整大小相应的数组。

INIT伪指令采用与DATA语句相同的参数。它的符号用作数组的基础,数据项以最新DYNT伪指令指定的偏移量安装在数组中。数组的大小会相应调整。 DYNT和INIT伪指令未在68020上实现。

定义程序

入口点由伪操作TEXT定义,伪操作TEXT以过程的名称(包括无处不在的(SB))和要在堆栈上预分配的自动存储的字节数为参数,当该变量通常为零时,编写汇编语言程序。在具有链接寄存器的计算机上,例如MIPS和SPARC,特殊值-4指示加载程序不生成PC保存和恢复指令,即使该功能不是叶子也是如此。这是一个返回其两个参数之和的完整过程:

文字总和(SB),$ 0

TEXT    sum(SB),$0

    MOVL    arg1+0(FP),R0

    ADDL    arg2+4(FP),R0

    RTS

TEXT伪操作的可选中间参数是加载程序选项的位字段。当为程序的其余部分启用了性能分析时,将1位置1会暂停性能分析。例如,

文字总和(SB),1,$ 0

TEXT    sum(SB),1,$0

    MOVL    arg1+0(FP),R0

    ADDL    arg2+4(FP),R0

    RTS

不会被剖析;上面的第一个版本是。状态特殊的子例程(例如系统调用例程)不应进行概要分析。

将2位置1可以在程序中定义同一TEXT符号的多个定义。加载程序只会在图像中放置一个这样的功能。它仅由Alef编译器发出。

即使是地址,从C调用的子例程也应将其结果放置在R0中。浮点值在F0中返回。将结构返回给C程序的函数将其存储结果的位置的地址作为其第一个参数。 R0在此类程序的调用协议中未使用。子例程负责保存其自己的寄存器,因此可以随意使用任何寄存器而无需保存它们(“调用者保存”)。 A6和A7是上述例外。

有疑问时

如果您感到困惑,请尝试对2c使用-S选项并编译示例程序。标准输出是对汇编器的有效输入。

使用说明

汇编器的指令集与机器的指令集不同。选择它以匹配编译器生成的内容,并根据操作系统的特定需求对其进行稍微扩展。例如,2a不能区分各种形式的MOVE指令:快速移动,移动地址等。相反,上下文可以完成这项工作。例如,

MOVL $ 1,R1

MOVL A0,R2

MOVW SR,R3

生成正式的MOVEQ,MOVEA和MOVESR指令。许多指令没有指定其全部功能所必需的语法。值得注意的示例是位域指令,乘法和除法指令等。有关生成的指令名的完整集合(以2a表示法,不是摩托罗拉的名称),请参见文件/sys/src/cmd/2c/2.out.h。尽管有其名称,该文件仍包含由编译器生成的中间文件中出现的指令的枚举,这些指令与汇编语言的各行完全对应。

放下指示

加载器修改由汇编器和编译器生成的代码。它折叠分支,复制短代码序列以消除分支,并丢弃无法访问的代码。假定每个功能的第一条指令都是可以到达的。您可能会在编译器输出中看到的伪指令NOP意味着根本没有指令,而不是什么都不做的指令。加载程序会丢弃所有NOP。

要生成真正的NOP指令或汇编程序未知的任何其他指令,请使用WORD伪指令。加载程序未安排有关RISC的此类指令,因此必须手动填充其延迟时间。

MIPS

寄存器仅按编号寻址:R0至R31。 R29是堆栈指针; R30用作静态基址指针,类似于68020上的A6。它的值是全局符号setR30(SB)的地址。包含子程序返回值的寄存器为R1。调用函数时,第一个参数的空间保留为0(FP),但在C(不是Alef)中,该值改为在R1中传递。

装载机将R28用作临时设备。系统使用R26和R27作为中断时间临时对象。因此,这些寄存器都不应该在用户代码中使用。

汇编程序不知道控制寄存器。而是将它们编号为寄存器M0,M1等。使用此技巧可访问状态:

#定义状态12

MOVW M(STATUS),R1

浮点寄存器称为F0至F31。按照惯例,必须将F24初始化为值0.0,将F26初始化为0.5,将F28初始化为1.0,将F30初始化为2.0。这是由操作系统完成的。

这些说明及其语法与制造商的手册不同。没有路易和亲属。而是有MOVW(移动字),MOVH(移动半字)和MOVB(移动字节)伪指令。如果操作数是无符号的,则指令为MOVHU和MOVBU。操作数的顺序是按照数据流顺序从左到右,就像68020一样,但是不像MIPS文档中那样。这意味着Bcond指令相对于这本书是相反的。例如,va BGTZ生成MIPS bltz指令。

汇编程序适用于R2000,R3000以及大多数R4000和R6000体系结构。它了解64位指令MOVV,MOVVL,ADDV,ADDVU,SUBV,SUBVU,MULV,MULVU,DIVV,DIVVU,SLLV,SRLV和SRAV。汇编器没有任何缓存,加载链接或存储条件指令。

加载程序将某些汇编程序指令扩展为多个指令。例如,加载器可以将32位常量的负载转换成lui,然后是ori。

汇编程序指令的布局应像没有负载,分支或浮点比较延迟槽一样;装载机将重新安排时间表,以确保正确性并提高性能。唯一的例外是,使用控制寄存器的指令的正确调度因机器的型号而异(并且通常没有文档记录),因此您应手动调度此类指令以保证正确的行为。加载程序生成

或非R0,R0,R0

需要真正的无操作指令时。手动调度代码时,请严格使用此指令;加载程序会识别它,并分别在代码之前和之后安排代码。同样,WORD伪操作的安排与无操作的一样。

NOSCHED伪操作禁用指令调度(默认情况下启用调度); SCHED重新启用它。对于未计划的指令,分支折叠,代码复制和无效代码消除被禁用。

SPARC

一旦了解了MIPS的Plan 9模型,就很熟悉SPARC。寄存器仅具有数字名称:R0至R31。忘记注册窗口:Plan 9根本不使用它们。机器有32个全局寄存器,每个周期。 R1 [sic]是堆栈指针。 R2是静态基址寄存器,其值是setSB(SB)的地址。 R7是返回寄存器,也是保存C(不是Alef)函数的第一个参数的寄存器,同样保留了0(FP)的空间。 R14是装载程序临时文件。

浮点寄存器与MIPS完全相同。

控制寄存器以诸如FSR之类的名称已知。访问这些寄存器的指令是MOVW指令,例如

MOVW Y,R8

用于SPARC指令

rdy%r8

移动指令类似于MIPS上的指令:伪操作,将其转换为适当的sethi指令,加法运算等顺序。指令从左至右读取。因为参数已翻转到SUBCC,所以条件代码不会像在MIPS上那样被反转。

ASI内容的语法例如是将单词从ASI 2中移出:

MOVW(R7,2),R8

双索引的语法是

MOVW(R7 + R8),R9

SPARC的指令调度类似于MIPS的调度。官方的禁止操作说明是:

ORN R0,R0,R0

i960

寄存器编号为R0至R31。堆栈指针是R29;返回寄存器是R4;静态基数为R28;它被初始化为setSB(SB)的地址。 R3必须为零;这应该在执行的早期手动完成

SUBO R3,R3

R27是装载程序临时文件。

不支持浮点。

不支持并且不能使用Intel调用约定。请改用BAL。说明主要与本书中的内容相同。主要变化是LOAD和STORE都称为MOV。 MOV的扩展字符与手册中的相同:O表示序数,W表示带符号等。

i386

汇编器采用32位保护模式。寄存器名称为SP,AX,BX,CX,DX,BP,DI和SI。堆栈指针(不是伪寄存器)是SP,返回寄存器是AX。没有物理帧指针,但是就MIPS而言,FP是用作帧指针的伪寄存器。

操作码名称与英特尔手册中列出的名称基本相同,并在其后附加L,W或B以标识32位,16位和8位操作。加载,存储和条件是例外。所有在通用寄存器,特殊寄存器(例如CR0,CR3,GDTR,IDTR,SS,CS,DS,ES,FS和GS)或存储器中加载和存储的操作码都写为

MOVx src,dst

其中x是L,W或B。因此要获得AL,请使用MOVB指令。如果需要访问AH,则必须在MOVB中明确提及它:

MOVB AH,BX

非法举动的例子很多,例如,

MOVB BP,DI

加载程序实际上实现为伪操作。

所有条件指令(J,SET)中的条件名称均遵循68020的约定,而不遵循Intel汇编程序的约定:JOS,JOC,JCS,JCC,JEQ,JNE,JLS,JHI,JMI,JPL,JPS,JPC ,JLT,JGE,JLE和JGT,而不是JO,JNO,JB,JNB,JZ,JNZ,JBE,JNBE,JS,JNS,JP,JNP,JL,JNL,JLE和JNLE。

寻址模式具有AX,(AX),(AX)(BX * 4),10(AX)和10(AX)(BX * 4)之类的语法。 AX的偏移量可以替换为FP或SB到访问名称的偏移量,例如extern + 5(SB)(AX * 2)。

其他说明:非相对JMP和CALL在语法上添加了*。只有LOOP,LOOPEQ和LOOPNE是合法的循环指令。仅REP和REPN是公认的中继器。这些不是前缀,而是字符串前面的独立操作码,例如

CLD; REP; MOVSL

不支持MOD / RM字段中的细分替换前缀。

AMD64

除非给出MODE伪操作,否则汇编器将采用64位模式:

模式$ 32

更改为32位模式。效果主要是诊断给定模式下的非法指令,但加载程序还将采用32位操作数和地址,以及32位PC值进行调用和返回。汇编程序的约定与上面的386相似。该体系结构提供了额外的定点寄存器R8至R15。所有寄存器均为64位,但指令访问低位8、16和32位,如处理器手册中所述。例如,MOVL to AX将一个值放在低32位中,并将高32位清除为零。文字操作数限制为有符号的32位值,在64位操作中将其符号扩展为64位。 MOVQ是例外,它允许使用64位文字。计划9 C中的外部寄存器是从R15向下分配的。

有许多新的指令,包括MMX和XMM媒体指令以及条件移动指令。 MMX寄存器为M0至M7,XMM寄存器为X0至X15。与386指令名称一样,所有新的64位整数指令以及MMX和XMM指令统一使用L表示“长字”(32位),而Q表示“四字”(64位)。有些指令将O(“八位字”)用于128位值,而处理器手册则使用O或DQ。汇编程序还始终将PL用于XMM指令中的“ packed long”,而不是Q,DQ或PI。即使寄存器为64位,也可以使用MOVL或MOVQ在控制寄存器之间来回移动值。汇编程序通常会接受手册的名称,以简化现有代码的转换(但请记住,操作数的顺序是源先是目标,然后才是目标)。

C的long long类型为64位,但是按值(而不是按引用)传递和返回。更值得注意的是,C指针值为64位,因此long long和unsigned long long是仅有足够宽的整数类型可以容纳指针值。 C编译器和库使用XMM浮点指令,而不是旧的387浮点指令,尽管后者是由汇编器和加载器实现的。与386不同,第一个整数或指针参数在寄存器中传递,该寄存器是整数或指针的BP(可以在汇编代码中由化名RARG引用)。 AX像以前一样保留子程序的返回值。尽管当前第一个浮点参数未在寄存器中传递,但浮点结果将在X0中返回。所有长度小于8个字节的参数都在堆栈上保留了8个字节的插槽,以保持对齐并简化可变长度参数列表访问,包括在传入寄存器时传递的第一个参数,即使未初始化字节4至7。

Power PC

Power PC遵循MIPS和SPARC设定的Plan 9模型,而不遵循复杂的ABI。支持60x和8xx PowerPC架构的32位指令。不支持较早的POWER指令。寄存器为R0至R31。 R0初始化为零;这是由C启动代码完成的,并由编译器和加载器承担。 R1是堆栈指针。 R2是静态基址寄存器,其值是setSB(SB)的地址。 R3是返回寄存器,也是保存C函数的第一个参数的寄存器,与MIPS一样,其空间保留为0(FP)。 R31是装载程序临时文件。计划9 C中的外部寄存器是从R30向下分配的。

浮点寄存器称为F0至F31。按照惯例,几个寄存器被初始化为特定的值。这是由操作系统完成的。 F27必须初始化为值0x4330000080000000(用于浮点到整数转换),F28初始化为值0.0,F29初始化为0.5,F30初始化为1.0,F31初始化为2.0。

与在MIPS和SPARC上一样,汇编器接受任意文字作为MOVW,ADD和其他存在“立即”变体的操作数,并且加载程序根据需要生成addi,addis,oris等序列。寄存器间接寻址模式使用与SPARC相同的语法,包括允许时的双索引。

指令名称通常来自摩托罗拉的指令名称,需要进行一些改动:将条件代码设置的“。”替换为CC,当字母“ o”表示“ OE = 1”时,将其替换为V。因此添加,addo。和subfzeo。成为ADD,ADDVCC和SUBFZEVCC。除了三操作数条件分支指令BC之外,汇编器还为以下常见情况提供伪指令:BEQ,BNE,BGT,BGE,BLT,BLE,BVC和BVS。无条件转移指令是BR。间接分支使用(CTR)或(LR)作为目标。

加载或存储操作通常以MOV变体替换:MOVW(移动字),MOVH(移动带符号扩展的半字)和MOVB(移动带符号扩展的字节,伪指令),并带有无符号变体MOVHZ和MOVBZ ,以及字节反转的MOVWBR和MOVHBR。 “加载或存储更新”版本是MOVWU,MOVHU和MOVBZU。加载或存储多个是MOVMW。例外是字符串指令,分别是LSW和STSW,以及保留指令lwarx和stwcx。,它们分别是LWAR和STWCCC,它们都具有按常规数据流顺序排列的操作数。浮点加载或存储指令为FMOVD,FMOVDU,FMOVS和FMOVSU。该寄存器用于注册移动指令fmr和fmr。分别写为FMOVD和FMOVDCC。

汇编器知道常用的专用寄存器:CR,CTR,DEC,LR,MSR和XER。其余的通常取决于体系结构,被称为SPR(n)。 60x系列的段寄存器类似地是SEG(n),但是n也可以是寄存器名称,如SEG(R3)中一样。在体系结构允许的情况下,专用寄存器和通用寄存器之间的移动被编写为MOVW,从而取代了mfcr,mtcr,mfmsr,mtmsr,mtspr,mfspr,mftb等。

条件寄存器CR的字段称为CR(0)至CR(7)。它们由产生mcrf或mtcrf的MOVFL(移动字段)伪指令使用。例如:

MOVFL CR(3),CR(0)

MOVFL R3,CR(1)

MOVFL R3,7美元,CR

例如,在条件分支指令中也接受它们

BEQ CR(7),标签

使用MOVFL以类似的方式访问FPSCR的字段:

MOVFL FPSCR,F0

MOVFL F0,FPSCR

MOVFL F0,$ 7,FPSCR

MOVFL $ 0,FPSCR(3)

酌情生成mffs,mtfsf或mtfsfi。

ARM

汇编器可通过R14和PC来访问R0。堆栈指针为R13,链接寄存器为R14,静态基址寄存器为R12。 R0是返回寄存器,也是保存子例程第一个参数的寄存器。计划9 C中的外部寄存器是从R10向下分配的。加载程序将R11用作临时寄存器。汇编器支持CPSR和SPSR寄存器。它还知道协处理器寄存器C0至C15。浮动寄存器为F0至F7,FPSR和FPCR。

与其他架构一样,加载和存储也称为MOV,例如MOVW用于加载字或存储字,而MOVM用于加载或存储多个字,具体取决于操作数。

指令的后缀支持寻址模式:.IA(之后递增)、. IB(之前递增)、. DA(之后递减)和.DB(之前递减)。这些只能与MOV指令一起使用。多重移动指令MOVM使用括号来定义寄存器范围,例如[R0-R12]。特殊的MOVM寻址模式位W,U和P以相同的方式写入,例如MOVM.DB.W。后缀.S允许MOVM指令在另一种处理器模式下访问用户R13和R14。二进制运算符<<(逻辑左移),>>(逻辑右移),->(算术右移)和@>(右旋转)支持寻址模式下的移位和旋转。例如R7 >> R2或R2 @> 2。汇编器不支持通过移位表达式进行索引;只有名称可以被双重索引。

任何指令之后都可以带有使该指令成为条件的后缀:例如.EQ,.NE等,如ARM手册中所述,例如,带有同义词.HS(对于.CS)和.LO(对于.CC)。 ADD.NE。在ARM允许下,算术和逻辑指令可以带有.S后缀来设置条件代码。

MCR和MRC协处理器指令的语法在很大程度上与手册中的内容相同,并进行了常规调整。汇编器仅直接支持编译器使用的ARM浮点协处理器操作:CMP,ADD,SUB,MUL和DIV,所有后缀都选择F或D后缀以选择单精度或双精度。浮点加载或存储成为MOVF和MOVD。转换指令也由移动指定:MOVWD,MOVWF,MOVDW,MOVWD,MOVFD和MOVDF。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章