笔者最近作业要求练习 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. 从零开始学Spring Boot系列-集成MySQL

    在Spring Boot中集成MySQL是为了让开发者能够轻松地与MySQL数据库进行交互.本篇文章将指导你如何在Spring Boot 3.2.3项目中使用Gradle来集成MySQL.在此之前,我 ...

  2. 【工具】用nvm管理nodejs版本切换,真香!

    前言 缘由 换个nodejs版本比换个媳妇还难,nvm堪称管理nodejs版本神器 事情的起因,公司的一些老项目需要依赖稳定老版本的nodejs,但是自己的一些项目所需要的是更高版本的nodejs,这 ...

  3. ConfigMap挂载与Subpath在Nginx容器中的应用

    本文分享自华为云社区<nginx.conf以configmap文件形式挂载到nginx容器中以及subpath使用场景>,作者:可以交个朋友. 背景 nginx.conf通过configm ...

  4. Java 多线程------多线程的创建(2),方式一:继承于Thread类

    1 package com.bytezero.threadexer; 2 3 /** 4 * 创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数 5 * 6 * 7 * ...

  5. clickhouse快速上手和问题记录

    clickhouse官方中文社区实战经验:手把手教你搭建单机clickhouse开发环境 我是用的是centOS7的虚拟机, 官方教程中的:sudo /etc/init.d/clickhouse-se ...

  6. vscode 合并分支 举例 master merge dev

    举例 将 dev 开发线 合并到 master 1 确定你在dev线,将dev代码改动全部提交 2 切换master,确定是最新代码,不确定就pull下,选择合并分支,见上图 3 在下拉的提示框中选择 ...

  7. 让 js 失效 Chrome F12 右上角 settings - Preferences - Debugger - Disable JavaScript

    说的可能比较长,实际上,F12 右上角 - 右小角 还是挺好找的.

  8. 基于泰凌微2.4G私有协议TLSR8359的遥控器解决方案之源码解析

    一 2.4G私有协议 在无线遥控和远距离无线通信领域,2.4G私有协议有着天然的优势.成本低,发射功率大,功耗低.这让它在远距离无线遥控飞机,遥控车等领域有着广泛的应用.基于TLSR8359市场上广泛 ...

  9. mysql迁移到pqsql笔记

    在将MySQL迁移到PostgreSQL的过程中,遇到了一些问题,下面是一些简单的解决方案. 使用命令,初始化数据库,并设置postgres的密码 bin\initdb -E UTF-8 -A md5 ...

  10. 记录--纯CSS实现骚气红丝带

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 在本文中,我们将探讨如何使用 CSS 以最少的代码创造出精美的 CSS 丝带形状,并最终实现下面这个效果: 下面我们使用html和css来 ...