如何输出一个整数来筛选 RISC-V 程序集

How to output an integer to screen risc-v assembly

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

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代理内核

spike pk example

没有生成输出。

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

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

并打印 42.

另外,如果我想打印数据段中num的内容,我该怎么办?

系统调用取决于环境。像 Venus 或 RARS 这样的“玩具”系统有自己的一套玩具系统调用,可以执行打印整数等操作。

在像 GNU/Linux 这样的真实系统中,您可以使用 ecall 访问的真实系统调用只能将字节复制到文件描述符。如果要输出文本,需要在user-space内存中创建文本,并传递一个指针给write系统调用

Spike + pk 显然更像 Linux,有一个 POSIX write(2) 系统调用,不像那些玩具系统调用环境,你可以在其中传递一个整数直接到 print-int ecallhttps://www.reddit.com/r/RISCV/comments/dagvzr/where_do_i_find_the_list_of_stdio_system_etc/ has some examples and links. Notably 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 字符串是 library 函数(如 printf 会做的事情。玩具系统没有库,因此它们只是将一些有用的函数作为系统调用。如果你想控制前导零或填充到固定宽度之类的东西,你必须自己写。 但是在像 Spike-pk 这样的系统上,您只有简单的类 Unix 系统调用并且(也许?)根本没有库,所以您必须 总是这样做你自己。

仅使用 Linux / Unix / Spike-pk 系统调用,您将需要重复除以 10 以获得二进制整数的十进制数字。就像 How do I print an integer in Assembly Level Programming without printf from the c library? 中显示 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/)。在堆栈上保留一个小缓冲区很方便。

Also, if I wanted to print the contents of num in the data segment, how would I go about it?

lw将数字存入寄存器,然后同上。

我根据 Peter Cordes 的回答找到了解决方案。我在这里发布实现以防有人需要它并供我自己参考。

更新:

步骤:

  1. 给定一个有符号数,求它的绝对值,如果是负数, 通过变量记录下来。
  2. 选择下一个点对齐的结束地址位置。
  3. 进行重复除法,将提醒存储在合适的内存位置。
  4. 如果数字为负数,则在开头添加'-'。
  5. 从末尾减去首地址得到长度。然后调用适当的系统调用。

可以找到系统调用here

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;
}

Risc-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