如何输出整数以筛选 risc-v 程序集



以下是我用于尝试打印到控制台的程序集:

global _start
_start:
addi   a0, x0, 1
addi   a1, x0, 42
addi   a7, x0, 63
ecall
addi   a0, x0, 0
addi   a7, x0, 93
ecall
.data
num:
.byte 6  

我编译了

riscv64-unknown-elf-as  -o example.o  example.S
riscv64-unknown-elf-ld  -o example  example.o

并使用尖峰代理内核运行

spike pk example

不生成任何输出。

这适用于 https://www.kvakil.me/venus/

addi   a0, x0, 1
addi   a1, x0, 42
ecall

并打印 42。

另外,如果我想在数据段中打印 num 的内容,我将如何去做?

我从彼得·科德斯的回答中设法了一个解决方案。我在这里发布实现,以防有人需要它并供我自己参考。

更新:

步骤:

  1. 给定一个有符号数字,获取其绝对值,如果该数字为负数, 通过变量记下它。
  2. 选择由下一个点对齐的结束地址位置。
  3. 执行重复除法并将提醒存储在适当的内存位置。
  4. 如果数字为负数,则在开头添加"-"。
  5. 通过从末尾减去第一个地址来获取长度。然后调用相应的系统调用。

可以在此处找到系统调用。

C 代码在逻辑上镜像程序集

#include <unistd.h>
void num_print(long num){
unsigned int base = 10;
int sign_bit = 0;
char string[20];
char* end = string + 19;
char* p   = end;
*p = 'n';

if (num < 0){
num = 0 - num;
sign_bit = 1;
}
do {
*(--p) = (num % base) + '0';
num /= base;
} while (num);
if (sign_bit)
*(--p) = '-';

size_t len = end - p;
write(1, p, len + 1);
}
int main(){
int arr[3] = {1234567, -1234567, 0};
for (int i=0; i < 3; i++){
num_print(arr[i]);
}
return 0;
}

里斯克-V 组装

.global _start
.text
_start:
la           s1, arr          # s1: load arr address
addi         s2, zero, 3      # s2: arr length
addi         sp, sp, -8       # push 1 item to stack 
sd           ra, 0(sp)        # save return address
mv           s3, zero         # s3: i loop counter  
j            compare_ipos
L1:
slli         s4, s3, 3        # s4: i * 8
add          s5, s1, s4       # s5: address of a[i]
ld           a0, 0(s5)        # a0: arr[i]
jal          ra, num_print    # call num_print
addi         s3, s3, 1        # increment i
compare_ipos:
blt          s3, s2, L1       # loop if i < 3
j            exit

num_print:
addi         sp, sp, -40      # create stack space
sd           s0, 32(sp)       # store frame pointer
addi         s0, sp, 40       # new frame pointer

addi         t0, zero, 0      # initialize sign_bit
addi         t1, zero, 10     # divisor and new-line char
addi         t2, s0, -16      # t2: string[n] 
add          a1, zero, t2     # a1: string[0] currently string[n]

addi         t3, zero, 'n'   # 'n' char
sb           t3, 0(a1)        # store 'n'

bge          a0, zero, PVE    # if num >= 0 go to L1 else get absolute
xori         a0, a0, -1       # (num ^ -1)
addi         a0, a0, 1        # num + 1
addi         t0, zero, 1      # set sign-bit to 1
PVE:
remu         t3, a0, t1       # num % 10
addi         t3, t3, 48       # convert to ascii
addi         a1, a1, -1       # decrement start pointer
sb           t3, 0(a1)        # store value
divu         a0, a0, t1       # num /= 10
blt          zero, a0, PVE    # if num > 0 loop
beq          t0, zero, print  # if sign_bit = 0 go to print else, add '-' char
addi         t3, zero, '-'    # ascii '-'
addi         a1, a1, -1       # decrement start pointer
sb           t3, 0(a1)        # store '-'
print:
sub          t4, t2, a1       # t4: len -- string[n] - string[0]
addi         a2, t4, 1        # len + 1
addi         a0, zero, 1      # file descriptor to write to
addi         a7, zero, 64     #  pk SYS_write
ecall                         # transfer control to os
ld           s0, 32(sp)       # restore frame pointer
addi         sp, sp, 40       # restore stack pointer
ret                           # return from function        

exit:
ld           ra, 0(sp)        # restore ra
addi         sp, sp, 8        # pop stack
addi         a0, zero, 0      # return value
addi         a7, zero, 93     # syscall exit code
ecall
.data
arr:
.dword  12345670, -12345670, 0

系统调用取决于环境。 像Venus或RARS这样的"玩具"系统有自己的一组玩具系统调用,可以执行诸如打印整数之类的操作。

在像 GNU/Linux 这样的现实世界系统中,您可以使用ecall访问的真正系统调用只能将字节复制到文件描述符中。 如果要输出文本,则需要在用户空间的内存中创建文本,并将指针传递给写入系统调用。

Spike +pk显然更像Linux,具有POSIXwrite(2)系统调用,而不是那些玩具系统调用环境,您可以在其中将整数直接传递给print-intecall。 https://www.reddit.com/r/RISCV/comments/dagvzr/where_do_i_find_the_list_of_stdio_system_etc/有一些示例和链接。 值得注意的是,https://github.com/riscv/riscv-pk/blob/master/pk/syscall.h 我们发现#define SYS_write 64作为write系统呼叫的呼叫号码(a7

)。write系统调用需要参数:write(int fd, const void *buf, size_t count)

将二进制整数格式化为 ASCII 字符串是函数(如printf)将要做的事情。 玩具系统没有库,所以他们只是把一些有用的函数作为系统调用。 如果你想控制诸如前导零或填充到固定宽度之类的东西,你必须自己编写。但是在像Spike-pk这样的系统上,你只有简单的类Unix系统调用,(也许?)根本没有库,所以你必须总是自己做。

仅使用Linux/Unix/Spike-pk系统调用,您需要重复除以10以获得二进制整数的十进制数字。 就像如何在程序集级编程中打印整数而不从 c 库中打印 printf 一样?其中显示了适用于 Linux 的 C 和 x86-64 程序集:

char *itoa_end(unsigned long val, char *p_end) {
const unsigned base = 10;
char *p = p_end;
do {
*--p = (val % base) + '0';
val /= base;
} while(val);                  // runs at least once to print '0' for val=0.
// write(1, p,  p_end-p);
return p;  // let the caller know where the leading digit is
}

转换为RISC-V汇编(或使用gcc或clang编译,例如通过 https://godbolt.org/)。 在堆栈上保留一个小缓冲区很方便。

另外,如果我想在数据段中打印 num 的内容,我将如何去做?

将数字lw到寄存器中,然后执行与上述相同的操作。

最新更新