龙芯技术社区
直播中

HonestQiao

8年用户 520经验值
擅长:嵌入式技术
私信 关注
[2K系列]

【广东龙芯2K500先锋板试用体验】LoongArch汇编初体验

龙芯2K500使用的是LoongArch指令集架构,是不同与现有的X86、Arm、Risc-V的指令集架构。

在官方资料库中,提供了下面的资料:

image.png

上述资料中,要了解LoongArch指令集架构的基础,可以从《龙芯架构参考手册-卷一基础架构.pdf》开始。

另外,《计算机体系结构基础(LoongArch)-3rd.pdf》也非常值得学习,是以LoongArch为基础详细讲解计算机硬件体系结构。

有了板子,有了书,对于深入理解计算机硬件和指令,可以理论联系实际,加深理解。

学用LoongArch的基础之一,就是学习LoongArch的汇编。

汇编通常分为两种,一种是裸机汇编,一种是在现有系统下的汇编。

裸机汇编难度较大,因为系统的方方面面,你都需要详细的了解,才能进行驱动控制。

在现有系统下的汇编,则可以调用现有系统的接口,将时间精力放在主要逻辑上。

官方为龙芯2K500提供的是一个裁剪版本的Linux,在此基础上编写汇编程序,可以调用Linux系统提供的系统调用。

在《LoongArch 系统调用(syscall)ABI.pdf》文件中,对“Linux/LoongArch 系统调用 ABI”进行了详细的说明,后面示例程序中,将会进行调用。

学习c/c++,从hello world开始;学习汇编,也同样可以从hello world开始。

编写前,需要先准备好编译环境。编译c/++使用的是GCC,编译汇编也可以用GCC。

可以参考《交叉工具链使用说明.pdf》使用“toolchain-loongarch64-linux-gnu-gcc8-host-x86_64-2022-07-18.tar.xz”来建立交叉编译环境。

也可以直接使用 foxsen/loongarch-assembly: assembly experiment environment for loongarch 提供的docker环境快速搭建。

一个标准的基础LoongArch汇编组成如下:

.section .data

.section .text
.global main
main:

其中:

  • .section表示定义段
  • .data表示定义数据段,例如字符串定义等
  • .text表示定义文本段,实际上就是我们程序逻辑部分
  • .global表示定义程序的入口,这里对应main

然后,我们编写一个简单的汇编文件,让运行后输出信息到stdout(标准输出)和stderr(标准错误输出)

.section c

string1: .asciz "Hello World!\r\nI'm LoongArch!\r\n"
string2: .asciz "Error:It is a test.\r\n"

.section .text
.global main
main:

    li.w $a7, 64              # write syscall number
    li.w $a0, 1               # stdout file descriptor == 1
    la $a1, string1           # string address
    li.w $a2, 32              # string len
    syscall 0x0               # syscall

    li.w $a7, 64              # write syscall number
    li.w $a0, 2               # stderr file descriptor == 2
    la $a1, string2            # string address
    li.w $a2, 22              # string len
    syscall 0x0               # syscall

    li.w $a7, 93              # exit syscall number
    li.w $a0, 0
    syscall 0x0               # syscall

我们先看看这个汇编最终的输出,然后再做讲解:

image.png

在上述运行过程中,如果不加任何参数(或者管道输出),会显示全部内容,这个内容,对应汇编里面.data部分的定义。

如果加了2>/dev/null,表示把stderr部分的输出,重定向到/dev/null,也就是不显示stderr的内容。

在Linux的系统调用中,输出内容的基础是write(),其具体定义如下:

write()
函数定义:ssize_t write (int fd, const void * buf, size_t count); 
函数说明:write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。
返回值:如果顺利write()会返回实际写入的字节数(len)。当有错误发生时则返回-1,错误代码存入errno中。

如果fd=1,则输出到标准输出stdout,如果fd=2,则输出到标注错误输出stderr。

在c/c++中,我们可以很方便的调用write()来输出内容。但是在汇编中,不能直接调用,需要通过系统ABI的方式来调用。

前面说过的《LoongArch 系统调用(syscall)ABI.pdf》就排上用场了。

image.png

上图中的__NR_write是一个数字,它就是系统调用号,实际对应write(),我们在汇编程序中,通过这个系统调用号来调用其实际对应的功能。

对应的代码如上述汇编代码:

li.w $a7, 64              # write syscall number
    li.w $a0, 1               # stdout file descriptor == 1
    la $a1, string1           # string address
    li.w $a2, 32              # string len
    syscall 0x0               # syscall

涉及到的寄存器,在手册中有说明:

image.png

li.w表示装载数据到寄存器,la表示传递地址

上述代码的逻辑就是,调用64号系统调用,也就是调用write(),并传入对应的参数:fd=1,buf对应string1,len为string1的长度(包含\0,所以为字符本身长度+1)

image.png

最后,通过上图中的LoongArch的syscall可以看到,之前的几步,正是为了设置对应的寄存器,然后最后完成调用,输出string1。

而输出string2的部分,则类似,只是设置$a0为2,表示输出到stderr。

程序的主要逻辑完成后,最后正常退出,并返回状态0,对应的汇编如下:

li.w $a7, 93              # exit syscall number
    li.w $a0, 0
    syscall 0x0               # syscall

系统调用编号93对应:

image.png

上述代码编写好了以后,保存为:hello-LoongArch.S,然后编译:

loongarch64-linux-gnu-gcc -static hello-LoongArch.S -o hello-LoongArch

最后会生成 hello-LoongArch ,将该文件传到龙芯2K500开发板,使用下面的指令运行,就能得到前面演示的结果了:

# 全部输出
./hello-LoongArch

# 错误信息输出到/dev/null
./hello-LoongArch 2>/dev/null

# 正常输出输出到/dev/null
./hello-LoongArch 1>/dev/null

image.png

参考资料:

更多回帖

发帖
×
20
完善资料,
赚取积分