简体   繁体   English

如何通过 output 和 integer 筛选 risc-v 程序集

[英]How to output an integer to screen risc-v assembly

The following is the assembly I have used in an attempt to print to console:以下是我用来尝试打印到控制台的程序集:

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  

I compiled with我编译了

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

and run using spike and proxy kernel并使用尖峰代理 kernel运行

spike pk example

No output is generated.没有生成 output。

This works on https://www.kvakil.me/venus/ with这适用于https://www.kvakil.me/venus/

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

and prints 42.并打印 42。

Also, if I wanted to print the contents of num in the data segment, how would I go about it?另外,如果我想打印数据段中num的内容,我会怎么做go呢?

I managed a solution from Peter Cordes' answer.我从 Peter Cordes 的回答中管理了一个解决方案。 I am posting the implementation here in case someone needs it and for my own reference.我在这里发布实现以防有人需要它并供我自己参考。

UPDATE:更新:

Steps:脚步:

  1. Given a signed number, get its absolute value, if the number is negative, make a note of it through a variable.给定一个有符号数,取其绝对值,若为负数,通过变量记下。
  2. Choose an end address position justified by the next point.选择一个由下一点对齐的结束地址 position。
  3. Perform repeated division and store the reminder in the appropriate memory location.执行重复划分并将提醒存储在适当的 memory 位置。
  4. If the number is negative add '-' at the beginning.如果数字为负数,则在开头添加“-”。
  5. Get length by subtracting first address from the end.通过从末尾减去第一个地址来获取长度。 Then call appropriate system call.然后调用适当的系统调用。

System calls can be found here .系统调用可以在这里找到。

C code logically mirrors the assembly 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 Assembly 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

System calls depend on the environment.系统调用取决于环境。 "Toy" systems like Venus or RARS have their own set of toy system calls that do things like print an integer.像 Venus 或RARS这样的“玩具”系统有自己的一套玩具系统调用,可以执行诸如打印 integer 之类的事情。

In a real-world system like GNU/Linux, true system calls that you can access with ecall can only copy bytes to a file descriptor.在像 GNU/Linux 这样的真实系统中,您可以使用ecall访问的真正系统调用只能将字节复制到文件描述符中。 If you want to output text, you need to create text in memory in user-space and pass a pointer to a write system call.如果要 output 文本,则需要在用户空间的 memory 中创建文本,并将指针传递给 write 系统调用。

Spike + pk is apparently more like Linux, with a POSIX write(2) system call, not like those toy system-call environments where you could pass an integer directly to a print-int ecall . Spike + pk显然更像 Linux,带有 POSIX write(2)系统调用,不像那些可以将 integer 直接传递给 print-int ecall的玩具系统调用环境。 https://www.reddit.com/r/RISCV/comments/dagvzr/where_do_i_find_the_list_of_stdio_system_etc/ has some examples and links. https://www.reddit.com/r/RISCV/comments/dagvzr/where_do_i_find_the_list_of_stdio_system_etc/有一些例子和链接。 Notably https://github.com/riscv/riscv-pk/blob/master/pk/syscall.h where we find #define SYS_write 64 as the call number (goes in a7 ) for a write system call.值得注意的是https://github.com/riscv/riscv-pk/blob/master/pk/syscall.h我们发现#define SYS_write 64作为调用号(在a7中)用于write系统调用。

A write system-call takes args: write(int fd, const void *buf, size_t count) . write系统调用需要 args: write(int fd, const void *buf, size_t count)

Formatting a binary integer into an ASCII string is something that library functions like printf will do.将二进制 integer 格式化为 ASCII 字符串是printf类的函数会做的事情。 Toy systems don't have a library, so they just put a few useful functions as system calls.玩具系统没有库,所以它们只是将一些有用的函数作为系统调用。 And if you want control over stuff like leading zeros or padding to a fixed width, you have to write it yourself.如果你想控制诸如前导零或填充到固定宽度之类的东西,你必须自己写。 But on a system like Spike-pk, you only have simple Unix-like system calls and (perhaps?) no library at all, so you have to always do it yourself.但是在像 Spike-pk 这样的系统上,你只有简单的类 Unix 系统调用和(也许?)根本没有库,所以你必须总是自己做。

With just Linux / Unix / Spike-pk system-calls, you'll want to do repeated division by 10 to get the decimal digits of a binary integer.仅使用 Linux / Unix / Spike-pk 系统调用,您需要重复除以 10 以获得二进制 integer 的十进制数字。 like in How do I print an integer in Assembly Level Programming without printf from the c library?就像如何在没有 c 库中的 printf 的情况下在装配级编程中打印 integer 中的一样? which shows C and x86-64 assembly for Linux:其中显示了 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
}

Translate to RISC-V assembly (or compile with gcc or clang, eg via https://godbolt.org/ ).转换为 RISC-V 程序集(或使用 gcc 或 clang 编译,例如通过https://godbolt.org/ )。 Reserving a small buffer on the stack is convenient.在堆栈上保留一个小缓冲区很方便。

Also, if I wanted to print the contents of num in the data segment, how would I go about it?另外,如果我想打印数据段中num的内容,我会怎么做go呢?

lw the number into a register, then do the same thing as above. lw将数字写入寄存器,然后执行与上述相同的操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM