MIPS指令集:汇编源程序(.S文件)编写

        我们学会了MIPS汇编语言后,编写汇编程序还需要一个“框架”,用“框架”的目的是让我们更加容易的编写汇编程序,把更复杂的任务,比如符号解析、地址重定向、对齐等工作交给工具链去完成。这个框架有2种方式,汇编源代码文件(以.S为后缀)和内嵌汇编(在.c文件中嵌入汇编语言的方式)。本篇介绍.S为后缀的汇编源程序的编写格式。

        我们可能经常遇到以.S后缀结尾和.s后缀结尾的文件。它们都是汇编源文件(可以作为gcc as汇编器的输入)。区别在于.S是GCC编译的汇编源代码文件。编译后生成的输出文件就是.s。之前介绍GCC编译过程时,我们知道一个.c文件的编译过程如下图白色框架部分所示。那么如果我们编写了.S文件,gcc就就省略了.c文件到.i文件的过程,可以直接对.S进行编译产生对应的.s文件。流程就如下图所示:

 

                          

图1:汇编源文件的编译过程

 

上图中的hello.S就是汇编源程序,里面可以按找汇编源程序的语法规则,使用MIPS机器指令和伪指令编写程序。本章将通过一个memcpy.S文件来了解MIPS汇编程序的编写规则。

 

一、汇编程序实例:memcpy.S

我们平时编程时经常会使用到libc库中的内存拷贝功能,接口如下:

void *memcpy(void *dest, const void *src, size_t n);

函数的功能是从源内存地址src的起始位置开始拷贝n个字节到目标内存地址dest中。这里编写MIPS汇编程序实现如下:
 

//memcpy.S

#include <sys/asm.h>

#include <sys/regdef.h>

.section .text

LEAF(my_memcpy)

.set push

.set mips64

dadd v0,zero,a0

daddiu t1,zero,0

loop:

beq t1,a2,exit   

nop

lb t2,0(a1)

sb t2,0(a0)

daddiu t1,t1,1

daddiu a0,a0,1

daddiu a1,a1,1

j loop

nop

exit:

jr ra

nop

.set pop

END(my_memcpy)

上面的第一行引用的头文件asm.h里定义了宏LEAF和END,分别代表了一个函数的开始和结束。具体定义如下:

#define LEAF(symbol)                            \

.globl symbol;                         \

.align 2;                              \

.type symbol,@function;               \

.ent symbol,0;                       \

symbol: .frame sp,0,ra;
# define END(function)                \

.end function;         \

.size function,.-function

        以“.”开头的都是仅仅给汇编器看的汇编指令,用于指导汇编器如何汇编,也称伪指令。宏定义LEAF(symbol)代表为叶子函数,叶子函数就是指不再调用其他子函数的函数,叶子函数通常使用宏定义LEAF(symbol)开头,使用宏定义END(symbol)结束。和叶子函数相对应的称为非叶子函数,非叶子函数通常使用宏定义ENTRY(symbol)开头,也使用宏定义END(symbol)结束。

         上面第二行引用的头文件regdef.h里面定义了t2、a0等寄存器的习惯名称。

         程序中的“loop :”代表的是标号 。标号都以“:”结尾,标号用于定义函数的入口点、中间的分支、数据存储位置。这里标号loop和下面的标号exit分别代表分支。

        程序中的指令“beq t1,a2,exit”。其中a2就是参数n,表示要拷贝的字节数。t1功能是计数器,初始值为0(daddiu t1,zero,0)。如果t1和a2相等,那么指令跳转到exit标识处执行返回操作。否则就要拷贝src(a1)中的一个字节到dest(a0)中,然后地址移位。就是指令:

lb t2,0(a1)

sb t2,0(a0)

daddiu a0,a0,1

daddiu a1,a1,1

        复制一个字节后,通过”j loop”跳转到loop标识处重新判断、拷贝字节。随着t1在后面的不断累加(daddiu t1,t1,1)到a2大小,指令就会执行到exit。

       这个memcpy.S写好后,我们可以通过编写一段c代码调用测试它。c代码如下:

//main.c

#include <stdio.h>

int main(){

    char* str = "function test \n";

    char dest[100];

    my_memcpy(dest,str,13);

    printf("%s \n",dest);

return 0;

}

编译运行命令如下:
 

# gcc main.c memcpy.S -o out

# ./out

function test

 

二、伪指令(汇编器指令)

        汇编器指令(Assembler Directives),是汇编语言中使用的一些操作符和助记符,还包括一些宏指令(如nop、dla、li等)。用于告诉汇编程序如何进行汇编,它既不控制机器的操作也不被汇编成机器代码,只能为汇编程序所识别并指导汇编如何进行,也称伪指令。

         所有汇编器指令的名称都以句点('.')开头,以“;”或者换行结尾。这些名称对大多数目标都不区分大小写,通常用小写字母书写。一些常用的的指令分类和功能如下:

符号/数据定义相关的指令:

  • .global symbol 声明symbol为全局变量。
  • .lobal symbol 声明symbol为局部变量。作用范围仅在当前文件内。
  • .extern symbol 声明一个外部符号。
  • .set symbol ,expression常量设置。比如

“.set mark,0x3”设定常数,类似c语言中的宏定义。

  • .byte 定义一个字节(8位)的地址空间。类似的指令还有.int、.long 、.word。空间大小依赖具体系统。
  • .string “STR”也是用于字符串定义。但是有尾端区分。
  • .ascii “string”... 将字符串组合成连续的地址。还有一个“.asciz”。.asciz会在字符串后自动添加结束符\0。
  • .rept count 重复count次扩展后面的指令,以.endr结尾。例如:
.rept 3

.int 0

.endr

这就相当于目标文件中会分配12个Byte的空间(int大小为4Byte*3次)。

  • .macro name args 宏定义,name 为宏名称,args为参数,以.endm结尾。例如:
.macro label l

\l:

.endm

汇编控制相关的指令:

  • .set symbol 汇编语言控制指令。例如:

“.set push”和“.set pop”分别用于设置的保存和恢复,表明其间的.set mips64设   置仅仅对当前这段代码起效。

“.set noat” 防止汇编器将汇编代码翻译成用到at($1) 寄存器的指令序列。

“.set nomacro” 防止汇编器将单个汇编语句翻译成多个指令。

“.set norecorder” 防止汇编器打乱代码次序。通常和“.set reorder”成对出现。

  • .text 告诉汇编器,把此后产生的代码放到目标文件中的“.text”段。
  • .section name 告诉汇编器,把此后产生的代码放到目标文件中的name段。比如:

.section .text 表明接下来的这段代码放到目标文件的.text段(代码段)。

.section .data 表明接下来的这段代码放到目标文件的.data段(数据段)。

关于段的概念请参考ELF文件格式说明,在本书最后一张将有介绍。

  • .ent 标识函数的起始点。
  • .end 标识函数的结尾,和.ent一样仅仅用于调试。
  • .size 表示在函数表中,function和所用指令的字节数一同列出。
  • .align n对指令或者数据的存放地址指定对齐方式。n取值因系统而异。
  • .type symbol @function 标识symbol 为函数名称。

 

三、MIPS相关的汇编伪指令

上面列举的汇编伪指令是和目标机器无关的伪指令。还有一些和目标机器相关的伪指令。下面列举一些MIPS体系架构相关的伪指令

  • .sym32 设置加载地址为32位

比如以MIPS的“dla $4,sym”宏指令为例(加载sym地址到寄存器$4),在用n64汇编器汇编出来的结果是:

lui     $4,%highest(sym)

lui     $1,%hi(sym)

daddiu  $4,$4,%higher(sym)

daddiu  $1,$1,%lo(sym)

dsll32  $4,$4,0

daddu   $4,$4,$1

也就是把sym地址按64位来处理,%highest(sym)获取的是sym的高16位(bit63-bit48)、%higher(sym)获取的是sym的bit47-bit32。%hi(sym)获取的是sym的bit31-bit16、%lo(sym)获取的是sym的低16位(bit15-bit0)。而如果要想指定sym32位处理,那么需要添加”.set sym32”指令,结果就是:

lui     $4,%hi(sym)

daddiu  $4,$4,%lo(sym)

这里“dla $4,sym”被叫做宏指令更合适,就是可以被汇编器根据实际情况扩展成多条机器指令的伪指令。这里“dla $4,sym”指令在32位机器上扩展成2条机器指令,在64位机器上被扩展成6条机器指令。

 

  • .set mipsn 兼容的指令版本

         n是一个从0到5的数字,或是数字32或64。1到5,32或64使汇编器从源程序中的这一点开始接受相应ISA级别的指令。比如.set mips3 告诉汇编器下面的指令是MIPS IV(64位指令集,兼容32位指令)中的指令。

  • .cpsetup .cpload
  • .set gp=64 和.set fp=64 允许指定目标文件中寄存器的大小。默认情况是.set gp=default .set fp=default。
  • .set hardfloat 开启使用硬件浮点指令。与其对应的是.set softfloat
  •  

更多的汇编指令可以参考GNU 汇编器开源社区https://sourceware.org/binutils/docs/as/的第7部分Assembler Directives。

 

 

 

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
MIPS(Microprocessor without Interlocked Pipeline Stages)是一种基于RISC(Reduced Instruction Set Computer)的指令集架构。MIPS指令集提供了丰富的指令,能够满足广泛的应用需求。 要下载MIPS汇编指令集,首先需要确定所需的具体版本。MIPS指令集有不同的版本和扩展,例如MIPS32、MIPS64、MIPS32 Release 6等。根据自己的需求选择相应的版本。 下载MIPS指令集可以通过多种途径实现。首先,可以通过访问MIPS技术公司的官方网站,在其开发者资源或者支持文档部分查找相关资料。官方网站通常提供最新的MIPS指令集文档和开发工具。 其次,可以在各大在线技术论坛、开发者社区或者开源软件平台搜索相关的MIPS指令集资料。这些平台上经常有热心的开发者分享自己编写汇编代码、教程和文档,可以从中获取所需的指令集信息。 另外,还可以通过参考书籍、学术论文、教育机构的课程资料等来源获取MIPS指令集相关的信息。这些资料通常会提供详细的解释和示例,有助于更好地理解和应用MIPS指令集。 无论是从官方渠道还是其他途径下载MIPS指令集,我们需要保证所获取的资料来源可靠,以确保指令集的准确性和合法性。 总之,下载MIPS汇编指令集可以通过访问官方网站、在线论坛、开源平台,以及参考相关书籍、论文等多种方式实现。选择可靠的来源,并根据需要选择合适的版本和资料,有助于理解和应用MIPS指令集

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海棠花败

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值