笔者最近作业要求练习 MIPS 汇编,熟悉 MIPS 汇编代码与 C 语言代码的对应关系。然而 SPIM/MARS 仿真器不能链接共享库以调用外部函数(如 stdio.h 下的函数),只能通过系统调用实现。C 语言可以通过内联汇编(Inline Assembly)实现系统调用而不借助任何外部函数,再将内联汇编语句封装成函数或宏函数,便于 C 程序调用。

内联汇编

内联汇编主要借助关键字 asm__asm__ (C99) 实现。内敛汇编语句基本格式:

__asm__ [volatile](汇编语句[:[输出结果]:[输入参数][:异常检测条件]]);

volatile 关键字用于防止编译器优化更改此处汇编代码;汇编语句填入汇编代码字符串;后面三类参数均可省,其中输出结果填入一个存结果的变量,输入参数填需要载入的变量或者供替换代码中占位符的有关值;异常检测可填入需要保持的寄存器,当寄存器被占用时,编译器会报错。

另外一个用法是用在 register 型变量之后,可以指定该变量对应哪个寄存器,如:

register int sys_id __asm__("$2") = 4;

这样对变量 sys_id 的取值/赋值等操作就等于对寄存器 $2 的读写操作。

以下是一些例子:

int a, b;
// a = a + b - 1;
// %0, %1, ... 就是占位符
__asm__ volatile(
"add %1,%1,%2\n\t"
"addi %0,%1,-1"
:"=r"(a)
:"r"(a),"r"(b));
// a = b < 0;
__asm__ volatile(
"slt %0, %1, $0"
:"=r"(a)
:"r"(b));
// printf("Hello");
register char *msg asm("$4") = "Hello";
__asm__ volatile(
"jal printf"
::"r"(msg));
// 此处一定要有 "r"(msg),否则编译器可能会认为变量 mmm 未被使用而忽略对该变量的赋值操作

宏函数

宏的本质就是代码段替换,只需要给一个代码段声明一个名称就可以在代码中反复使用这一代码段。代码段可以是最基础的字面常量等,也可以是稍复杂的多条语句(如宏函数)。当然,宏也可以简化一些语句,甚至可以用宏实现 try-catch 语句[1]

常见宏函数的声明形式如下:

// 无“返回值”型
#define 函数名([参数列表])\
{\
代码段;\
} // 有“返回值”型。这里用到了括号的一个语法
#define 函数名([参数列表])\
({\
代码段;\
返回值(右值表达式);\
})

另外,参数列表是可选项,没有类型限制,甚至也可以是代码段。

宏定义 Syscall 内联汇编

SPIM 仿真器的 MIPS 系统调用参数:

服务 系统调用代码 参数 结果
print_int 1 $a0=integer
print_float 2 $f12=float
print_double 3 $f12=double
print_string 4 $a0=string
read_int 5 integer (in $v0)
read_float 6 float (in $v0)
read_double 7 double (in $v0)
read_string 8 $a0=buffer, $a1=length
sbrk 9 $a0=amount address (in $v0)
exit 10
print_char 11 $a0=char
read_char 12 char (in $v0)
open 13 $a0=filename(string), $a1=flags, $a2=mode file descriptor (in $a0)
read 14 $a0=file descriptor, $a1=buffer, $a2=length num chars read (in $a0)
write 15 $a0=file descriptor, $a1=buffer, $a2=length num chars written (in $a0)
close 16 $a0=file descriptor
exit2 17 $a0=result

用上述两种宏函数定义方式定义其中几个常用的系统调用,如下:

#define sys_open(pth, fg) ({\
register int _ID_ __asm__("$2") = 13, _FG_ __asm__("$5") = fg;\
register char *_PTH_ __asm__("$4") = pth;\
__asm__ volatile("syscall"\
:"=r"(_ID_):"r"(_ID_),"r"(_PTH_),"r"(_FG_));\
_ID_;}) #define sys_print_string(str) {\
register int _ID_ __asm__("$2") = 4;\
register char *_STR_ __asm__("$4") = str;\
__asm__ volatile("syscall"\
::"r"(_ID_),"r"(_STR_));} #define sys_print_int(i) {\
register int _ID_ __asm__("$2") = 1, _I_ __asm__("$4") = i;\
__asm__ volatile("syscall"::"r"(_ID_),"r"(_I_));} #define sys_read_int() ({\
register int _ID_ __asm__("$2") = 5;\
__asm__ volatile("syscall"\
:"=r"(_ID_):"r"(_ID_));\
_ID_;}) #define sys_read(fd, buf, len) ({\
register int _ID_ __asm__("$2") = 14, _FD_ __asm__("$4") = fd, _LEN_ __asm__("$6") = len;\
register char *_BUF_ __asm__("$5") = buf;\
__asm__ volatile("syscall"\
:"=r"(_ID_):"r"(_ID_),"r"(_FD_),"r"(_BUF_),"r"(_LEN_));\
_ID_;}) #define sys_close(fd) {\
register int _ID_ __asm__("$2") = 16, _FD_ __asm__("$4") = fd;\
__asm__ volatile("syscall"::"r"(_ID_),"r"(_FD_));} #define sys_exit() {\
register int _ID_ __asm__("$2") = 10;\
__asm__ volatile("syscall"::"r"(_ID_));}

编译测试

老师推荐用在线平台 https://godbolt.org 编译测试,其实本地用 mips-linux-gnu-gcc 交叉编译也行。将以上宏定义存为头文件 mips-syscall.h,然后在代码中引用,进行简单的测试:

#include "mips-syscall.h"

void main() {
sys_print_string("Input a number: ");
int n = sys_read_int();
sys_print_string("The number is ");
sys_print_int(n);
sys_exit();
}

由于 SPIM/MARS 仿真器的执行入口和一般程序不太一样,而且需要调用 exit 来结束程序,所以以上代码的驻韩数写法比较怪。

本地交叉编译,编译器 mips-linux-gnu-gcc 12.3.0,编译参数 -O2 -S -o m.s,去掉不相关字段:

	.data
$LC0:
.ascii "Input a number: \000"
$LC1:
.ascii "The number is \000"
.text
main:
lw $4,%got($LC0)($28)
li $2,4 # 0x4
addiu $4,$4,%lo($LC0)
syscall
li $2,5 # 0x5
syscall
lw $4,%got($LC1)($28)
move $3,$2
li $2,4 # 0x4
addiu $4,$4,%lo($LC1)
syscall
li $2,1 # 0x1
move $4,$3
syscall
li $2,10 # 0xa
syscall
jr $31

可以看到已经成功编译,同时宏函数也都被替换为相应的系统调用。再经过一些调整后得到 MARS 可用的代码,运行测试,结果如下:

Input a number: 9
The number is 9
-- program is finished running --

  1. https://zhuanlan.zhihu.com/p/245642367

C 语言宏 + 内联汇编实现 MIPS 系统调用的更多相关文章

  1. Linux C中内联汇编的语法格式及使用方法(Inline Assembly in Linux C)【转】

    转自:http://www.linuxidc.com/Linux/2013-06/85221p3.htm 阅读Linux内核源码或对代码做性能优化时,经常会有在C语言中嵌入一段汇编代码的需求,这种嵌入 ...

  2. ARM嵌入式开发中的GCC内联汇编__asm__

    在针对ARM体系结构的编程中,一般很难直接使用C语言产生操作协处理器的相关代码,因此使用汇编语言来实现就成为了唯一的选择.但如果完全通过汇编代码实现,又会过于复杂.难以调试.因此,C语言内嵌汇编的方式 ...

  3. C语言的本质(32)——C语言与汇编之C语言内联汇编

    用C写程序比直接用汇编写程序更简洁,可读性更好,但效率可能不如汇编程序,因为C程序毕竟要经由编译器生成汇编代码,尽管现代编译器的优化已经做得很好了,但还是不如手写的汇编代码.另外,有些平台相关的指令必 ...

  4. C语言中递归什么时候能够省略return引发的思考:通过内联汇编解读C语言函数return的本质

    事情的经过是这种,博主在用C写一个简单的业务时使用递归,因为粗心而忘了写return.结果发现返回的结果依旧是正确的.经过半小时的反汇编调试.证明了我的猜想,如今在博客里分享.也是对C语言编译原理的一 ...

  5. 通过调用C语言的库函数与在C代码中使用内联汇编两种方式来使用同一个系统调用来分析系统调用的工作机制

    通过调用C语言的库函数与在C代码中使用内联汇编两种方式来使用同一个系统调用来分析系统调用的工作机制 前言说明 本篇为网易云课堂Linux内核分析课程的第四周作业,我将通过调用C语言的库函数与在C代码中 ...

  6. 最牛X的GCC 内联汇编

    导读 正如大家知道的,在C语言中插入汇编语言,其是Linux中使用的基本汇编程序语法.本文将讲解 GCC 提供的内联汇编特性的用途和用法.对于阅读这篇文章,这里只有两个前提要求,很明显,就是 x86 ...

  7. GCC内联汇编入门

    原文为GCC-Inline-Assembly-HOWTO,在google上可以找到原文,欢迎指出翻译错误. 中文版说明 由于译者水平有限,故译文出错之处,还请见谅.C语言的关键字不译,一些单词或词组( ...

  8. [翻译] GCC 内联汇编 HOWTO

    目录 GCC 内联汇编 HOWTO 原文链接与说明 1. 简介 1.1 版权许可 1.2 反馈校正 1.3 致谢 2. 概览 3. GCC 汇编语法 4. 基本内联 5. 扩展汇编 5.1 汇编程序模 ...

  9. GNU C内联汇编(AT&amp;T语法)

    转:http://www.linuxso.com/linuxbiancheng/40050.html 内联汇编提供了可以在C或C++代码中创建汇编语言代码,不必连接额外的库或程序.这种方法对最终程序在 ...

  10. 汇编语言---GCC内联汇编

    转:http://www.cnblogs.com/taek/archive/2012/02/05/2338838.html GCC支持在C/C++代码中嵌入汇编代码,这些代码被称作是"GCC ...

随机推荐

  1. 浅入kubernetes(3):namespace、node、pod

    目前已经完成三篇关于 kubernetes 的文章: 在 Ubuntu 上安装 K8S教程 浅入kubernetes(1):Kubernetes 入门基础 浅入kubernetes(2):Kubern ...

  2. STL-priority_queue模拟实现

    #include<deque> //测试用 #include<vector>//测试用 #include"9Date.h"//测试用 #include< ...

  3. Effective C++ 第一章:让自己习惯C++

    Effective C++ 第一章:让自己习惯C++ 引言 最近在阅读这本<effective C++ 改善程序与设计的55个具体做法>这本书,为了以后忘记的时候回顾,写一些笔记,每次笔记 ...

  4. vue table 里面 slot 的模板复用 slot-scope template v-for

    vue table 里面 slot 的模板复用 slot-scope template v-for 需求 经常在table里面要有自定义列,但是会有相同的自定义列,这个时候又不想写很多一样的templ ...

  5. pod常见的非故障及故障状态解析

    在Kubernetes中,Pod的状态可以反映其当前的生命周期状态.是否正常运行或遇到了某些状况.以下是一些Pod常见的非故障状态: Running:这是Pod最常见的非故障状态,表示Pod已经成功调 ...

  6. 2024 SemEval 冠军

    SemEval Task10,本质是一个文本分类的任务,有三个子任务,论文摘要如下:在多维对话中,情绪不仅作为情感交流的重要中介者,还承载着丰富的信息.因此,准确识别交流者的情绪并理解情绪变化的触发因 ...

  7. KTL 一个支持C++14编辑公式的K线技术工具平台

    K,K线,Candle蜡烛图. T,技术分析,工具平台 L,公式Language语言使用c++14,Lite小巧简易. 项目仓库:https://github.com/bbqz007/KTL 国内仓库 ...

  8. stream使用汇总

    整理了下java使用stream处理list的几个便捷的方法 准备数据 List<KnowledgeInfoTable> knowledgeInfoTables = knowledgeIn ...

  9. Spring Boot学习日记

    学习了springboot 的优点 为所有Spring开发者更快的入门 开箱即用,提供各种默认配置来简化项目配置 内嵌式容器简化Web项目 没有冗余代码生成和XML配置的要求 Spring开发-Hel ...

  10. Android组件化开发实践和案例分享

    目录介绍 1.为什么要组件化 1.1 为什么要组件化 1.2 现阶段遇到的问题 2.组件化的概念 2.1 什么是组件化 2.2 区分模块化与组件化 2.3 组件化优势好处 2.4 区分组件化和插件化 ...