C 语言宏 + 内联汇编实现 MIPS 系统调用
笔者最近作业要求练习 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 --
C 语言宏 + 内联汇编实现 MIPS 系统调用的更多相关文章
- Linux C中内联汇编的语法格式及使用方法(Inline Assembly in Linux C)【转】
转自:http://www.linuxidc.com/Linux/2013-06/85221p3.htm 阅读Linux内核源码或对代码做性能优化时,经常会有在C语言中嵌入一段汇编代码的需求,这种嵌入 ...
- ARM嵌入式开发中的GCC内联汇编__asm__
在针对ARM体系结构的编程中,一般很难直接使用C语言产生操作协处理器的相关代码,因此使用汇编语言来实现就成为了唯一的选择.但如果完全通过汇编代码实现,又会过于复杂.难以调试.因此,C语言内嵌汇编的方式 ...
- C语言的本质(32)——C语言与汇编之C语言内联汇编
用C写程序比直接用汇编写程序更简洁,可读性更好,但效率可能不如汇编程序,因为C程序毕竟要经由编译器生成汇编代码,尽管现代编译器的优化已经做得很好了,但还是不如手写的汇编代码.另外,有些平台相关的指令必 ...
- C语言中递归什么时候能够省略return引发的思考:通过内联汇编解读C语言函数return的本质
事情的经过是这种,博主在用C写一个简单的业务时使用递归,因为粗心而忘了写return.结果发现返回的结果依旧是正确的.经过半小时的反汇编调试.证明了我的猜想,如今在博客里分享.也是对C语言编译原理的一 ...
- 通过调用C语言的库函数与在C代码中使用内联汇编两种方式来使用同一个系统调用来分析系统调用的工作机制
通过调用C语言的库函数与在C代码中使用内联汇编两种方式来使用同一个系统调用来分析系统调用的工作机制 前言说明 本篇为网易云课堂Linux内核分析课程的第四周作业,我将通过调用C语言的库函数与在C代码中 ...
- 最牛X的GCC 内联汇编
导读 正如大家知道的,在C语言中插入汇编语言,其是Linux中使用的基本汇编程序语法.本文将讲解 GCC 提供的内联汇编特性的用途和用法.对于阅读这篇文章,这里只有两个前提要求,很明显,就是 x86 ...
- GCC内联汇编入门
原文为GCC-Inline-Assembly-HOWTO,在google上可以找到原文,欢迎指出翻译错误. 中文版说明 由于译者水平有限,故译文出错之处,还请见谅.C语言的关键字不译,一些单词或词组( ...
- [翻译] GCC 内联汇编 HOWTO
目录 GCC 内联汇编 HOWTO 原文链接与说明 1. 简介 1.1 版权许可 1.2 反馈校正 1.3 致谢 2. 概览 3. GCC 汇编语法 4. 基本内联 5. 扩展汇编 5.1 汇编程序模 ...
- GNU C内联汇编(AT&T语法)
转:http://www.linuxso.com/linuxbiancheng/40050.html 内联汇编提供了可以在C或C++代码中创建汇编语言代码,不必连接额外的库或程序.这种方法对最终程序在 ...
- 汇编语言---GCC内联汇编
转:http://www.cnblogs.com/taek/archive/2012/02/05/2338838.html GCC支持在C/C++代码中嵌入汇编代码,这些代码被称作是"GCC ...
随机推荐
- 压测中TPS上不去的几种原因及分析?
1. 服务器资源限制:服务器的硬件资源(如 CPU.内存.磁盘)可能不足以处理大量的请求.在高负载情况下,服务器可能无法及时响应所有的请求,导致 TPS 上不去.解决方法可以考虑升级硬件资源或通过负载 ...
- Java 递归方法的使用 + 例子
1 /* 2 * 递归方法的使用 3 * 1.递归方法:一个方法体内调用它自身 4 * 2.方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制 5 * 递归一定要想已知方向 ...
- C++ //类模板中成员函数创建时机 //类模板中成员函数和普通类中成员函数创建时机是有区别的: //1.普通类中的成员函数一开始就可以创建 //2.类模板中的成员函数在调用时才创建
1 //类模板中成员函数创建时机 2 //类模板中成员函数和普通类中成员函数创建时机是有区别的: 3 //1.普通类中的成员函数一开始就可以创建 4 //2.类模板中的成员函数在调用时才创建 5 6 ...
- Linux管理SpringBoot应用shell脚本实现
Liunx系统如何部署和管理SpringBoot项目应用呢?最简单的方法就是写个shell脚本. Spring Boot是Java的一个流行框架,用于开发企业级应用程序.下面我们将学习如何在Lin ...
- liunx 大文件切割,catalina.out 大文件打开
工作中,由于没有没有配日志文件切割,不小心日志文件上G了,用tail -f 或 cat 命令都难打开了,但偏这时候出了点事,需要查日志 怎么呢.第一条件命令 tail -50000f ca ...
- day03-1-查看账单&结账功能
满汉楼03 4.功能实现05 4.8查看账单功能 按照之间搭建起来的框架,在BillService编写方法 4.8.1代码实现 1.修改Bill类 重写Bill类中的toString方法 @Overr ...
- js实现展开多级数组
1.递归 function steamrollArray(arr) { let res = [] for (const a of arr) { if(a instanceof Array){ res ...
- .NET开源免费的Windows快速文件搜索和应用程序启动器
前言 今天大姚给大家分享一款.NET开源(MIT License).免费.功能强大的Windows快速文件搜索和应用程序启动器:Flow Launcher. 工具介绍 Flow Launcher 是一 ...
- PS-AXI-GPIO-流水灯设计
PS-AXI-GPIO-流水灯设计 1.实验目的 在了解了AXI协议的基本内容后,通过已经设计好的AXI的IP核来了解实际设计中AXI的工作原理和设计原理是必要的.这个实验以前实际上按照教程做过,但是 ...
- SQL调优系列--数据严重倾斜的连接优化
背景 对于两个大表关联的场景,如果过滤条件的列值,存在高度倾斜,可以考虑根据反向滤值,进行过滤操作,减少连接的CPU时间. 数据准备 -- 状态表 tp01_state 记录 大表tp01 记录的多种 ...