从头开始RISC-V

在本文中,我们将通过RISC-V体系结构及其生态系统来探讨各种底层概念(编译和布局,原始运行时,汇编器等)。 我本人是一名Web开发人员,我什么都不做,但这对我来说很有趣,这是文章的来源! 和我一起在这低级混乱的深处进行忙碌的旅程。

首先,让我们谈谈RISC-V及其架构的重要性,设置RISC-V工具链并在仿真的RISC-V硬件上运行一个简单的C程序。

目录内容


  1. 什么是RISC-V?
  2. 配置QEMU和RISC-V工具
  3. 嗨,RISC-V!
  4. 天真的方法
  5. 提起窗帘-v
  6. 搜索我们的堆栈
  7. 布局图
  8. 别说了 锤击时间! 运行时!
  9. 调试但现在是真实的
  10. 接下来是什么?
  11. 选配

什么是RISC-V?


RISC-V是一种免费的指令集体系结构。 该项目于2010年起源于加利福尼亚大学伯克利分校。 代码的开放性和使用自由度在其成功中扮演着重要角色,这与许多其他体系结构有很大不同。 以ARM为例:要创建兼容的处理器,您必须支付100万到1000万美元的预付款,还需支付销售收入的0.5-2%的特许权使用费 。 自由开放的模型使RISC-V对许多人来说都是有吸引力的选择,包括那些无法为ARM或其他处理器支付许可证的初创公司,学术研究人员以及(显然)开源社区。

RISC-V的迅速普及并没有引起人们的注意。 ARM 推出了一个站点 (尝试(但没有成功))强调ARM优于RISC-V的优势(该站点已关闭)。 RISC-V项目得到了许多大公司的支持,包括Google,Nvidia和Western Digital。

配置QEMU和RISC-V工具


在设置环境之前,我们无法在RISC-V处理器上运行代码。 幸运的是,这不需要物理RISC-V处理器;相反,我们采用qemu 。 按照说明安装操作系统 。 我有MacOS,因此只需输入一个命令:

# also available via MacPorts - `sudo port install qemu` brew install qemu 

方便地, qemu附带了几台现成的计算机 (请参阅qemu-system-riscv32 -machine )。

接下来,安装适用于RISC-V和RISC-V工具的OpenOCD

在此处下载RISC-V OpenOCD和RISC-V工具的现成组件。
我们将文件解压缩到任何目录, ~/usys/riscv 。 记住它以备将来使用。

 mkdir -p ~/usys/riscv cd ~/Downloads cp openocd-<date>-<platform>.tar.gz ~/usys/riscv cp riscv64-unknown-elf-gcc-<date>-<platform>.tar.gz ~/usys/riscv cd ~/usys/riscv tar -xvf openocd-<date>-<platform>.tar.gz tar -xvf riscv64-unknown-elf-gcc-<date>-<platform>.tar.gz 

设置环境变量RISCV_OPENOCD_PATHRISCV_PATH以便其他程序可以找到我们的工具链。 根据操作系统和外壳,这可能看起来有所不同:我将路径添加到~/.zshenv

 # I put these two exports directly in my ~/.zshenv file - you may have to do something else. export RISCV_OPENOCD_PATH="$HOME/usys/riscv/openocd-<date>-<version>" export RISCV_PATH="$HOME/usys/riscv/riscv64-unknown-elf-gcc-<date>-<version>" # Reload .zshenv with our new environment variables. Restarting your shell will have a similar effect. source ~/.zshenv 

/usr/local/bin中为此可执行文件创建一个符号链接,以便您可以随时运行它,而无需指定~/usys/riscv/riscv64-unknown-elf-gcc-<date>-<version>/bin/riscv64-unknown-elf-gcc

 # Symbolically link our gcc executable into /usr/local/bin. Repeat this process for any other executables you want to quickly access. ln -s ~/usys/riscv/riscv64-unknown-elf-gcc-8.2.0-<date>-<version>/bin/riscv64-unknown-elf-gcc /usr/local/bin 

瞧,我们有一个有效的RISC-V工具包! 我们所有的可执行文件,例如riscv64-unknown-elf-gccriscv64-unknown-elf-gdbriscv64-unknown-elf-ld等,都位于~/usys/riscv/riscv64-unknown-elf-gcc-<date>-<version>/bin/

嗨,RISC-V!


2019年5月26日补丁:

不幸的是,由于RISC-V QEMU中的错误,QEMU中的自由e-sdk “ hello world”程序不再起作用。 已经发布了一个补丁程序来解决此问题,但现在请跳过本节。 本文的后续部分将不需要此程序。 修复错误后,我会跟踪情况并更新文章。

有关更多信息,请参见此评论

通过设置工具,让我们运行简单的RISC-V程序。 让我们从克隆SiFive Freedom -e-sdk存储库开始:

 cd ~/wherever/you/want/to/clone/this git clone --recursive https://github.com/sifive/freedom-e-sdk.git cd freedom-e-sdk 

按照传统 ,让我们从freedom-e-sdk存储库中的“ Hello,world”程序开始。 我们使用他们提供的现成的Makefile在调试模式下编译该程序:

 make PROGRAM=hello TARGET=sifive-hifive1 CONFIGURATION=debug software 

并在QEMU中运行:

 qemu-system-riscv32 -nographic -machine sifive_e -kernel software/hello/debug/hello.elf Hello, World! 

这是一个很好的开始。 您可以从freedom-e-sdk运行其他示例。 之后,我们将编写并尝试使用C调试我们自己的程序。

天真的方法


让我们从一个简单的程序开始,该程序无限地将两个数字相加。

 cat add.c int main() { int a = 4; int b = 12; while (1) { int c = a + b; } return 0; } 

我们要运行此程序,首先需要为RISC-V处理器编译它。

 # -O0 to disable all optimizations. Without this, GCC might optimize # away our infinite addition since the result 'c' is never used. # -g to tell GCC to preserve debug info in our executable. riscv64-unknown-elf-gcc add.c -O0 -g 

这将创建一个a.out文件,该文件gcc默认为可执行文件。 现在在qemu运行此文件:

 # -machine tells QEMU which among our list of available machines we want to # run our executable against. Run qemu-system-riscv64 -machine help to list # all available machines. # -m is the amount of memory to allocate to our virtual machine. # -gdb tcp::1234 tells QEMU to also start a GDB server on localhost:1234 where # TCP is the means of communication. # -kernel tells QEMU what we're looking to run, even if our executable isn't # exactly a "kernel". qemu-system-riscv64 -machine virt -m 128M -gdb tcp::1234 -kernel a.out 

我们选择了riscv-qemu 最初随附virt机。

现在,我们的程序在QEMU中与localhost:1234上的GDB服务器一起运行,我们从另一个终端通过RISC-V GDB客户端连接到它:

 # --tui gives us a (t)extual (ui) for our GDB session. # While we can start GDB without any arguments, specifying 'a.out' tells GDB # to load debug symbols from that file for the newly created session. riscv64-unknown-elf-gdb --tui a.out 

而且我们在GDB内部!

 此GDB配置为“ --host = x86_64-apple-darwin17.7.0 --target = riscv64-unknown-elf”。  │
键入“显示配置”以获取配置详细信息。  │
有关错误报告的说明,请参见:│
 <http://www.gnu.org/software/gdb/bugs/>。  │
在线查找GDB手册和其他文档资源:│
     <http://www.gnu.org/software/gdb/documentation/>。  │
                                                                                                       │
要获得帮助,请键入“帮助”。  │
输入“ apropos word”以搜索与“ word”相关的命令...│
从a.out读取符号...│
 (gdb) 

我们可以尝试在GDB中为a.out可执行文件运行runstart命令,但是目前由于明显的原因,该命令无法正常工作。 我们将该程序编译为riscv64-unknown-elf-gcc ,因此主机应在riscv64体系结构上运行。

但是有出路! 这种情况是存在GDB客户服务器模型的主要原因之一。 我们可以获取riscv64-unknown-elf-gdb可执行文件,而不是在主机上启动它,而是为它指定一些远程目标(GDB服务器)。 您还记得,我们刚刚启动riscv-qemu并告诉我们在localhost:1234上启动GDB服务器localhost:1234 。 只需连接到该服务器:

  (GDB)目标远程:1234│
远程调试使用:1234 

现在您可以设置一些断点:

 (gdb) b main Breakpoint 1 at 0x1018e: file add.c, line 2. (gdb) b 5 # this is the line within the forever-while loop. int c = a + b; Breakpoint 2 at 0x1019a: file add.c, line 5. 

最后,指定GDB continue (缩写为c ),直到到达断点:

 (gdb) c Continuing. 

您会很快注意到该过程不会以任何方式结束。 这很奇怪……我们不应该立即到达断点b 5吗? 发生什么事了



在这里您可以看到几个问题:

  1. 文本UI找不到源。 该界面应显示我们的代码和附近的任何断点。
  2. GDB看不到当前执行行( L?? ),并显示计数器0x0( PC: 0x0 )。
  3. 输入行中的某些文本全部如下所示: 0x0000000000000000 in ?? () 0x0000000000000000 in ?? ()

结合我们无法达到断点的事实,这些指标表明:我们做错了什么 。 但是呢

提起窗帘-v


要了解正在发生的事情,您需要退后一步,谈论我们的简单C程序实际上是如何工作的。 main函数做一个简单的加法,但实际上是什么呢? 为什么要称其为“ main ”而不是“ origin或“ begin ? 根据约定,所有可执行文件都开始使用main函数执行,但是什么魔术提供了这种功能?

要回答这些问题,让我们用-v标志重复我们的GCC团队,以获取实际情况的更详细输出。

 riscv64-unknown-elf-gcc add.c -O0 -g -v 

输出很大,因此我们将不会查看整个清单。 重要的是要注意,尽管GCC正式是编译器,但它也默认为编译(要限制自己进行编译和汇编,必须指定-c标志)。 为什么这很重要? 好吧,看看gcc的详细输出中的代码片段:

  #实际的`gcc -v`命令输出完整路径,但是这些路径相当
 #很长,因此假装这些变量存在。
 #$ RV_GCC_BIN_PATH = /用户/ twilcock / usys / riscv / riscv64-unknown-elf-gcc- <日期>-<版本> / bin /
 #$ RV_GCC_LIB_PATH = $ RV_GCC_BIN_PATH /../ lib / gcc / riscv64-unknown-elf / 8.2.0

 $ RV_GCC_BIN_PATH /../ libexec / gcc / riscv64-unknown-elf / 8.2.0 / collect2 \
   ...被截断... 
   $ RV_GCC_LIB_PATH /../../../../ riscv64-unknown-elf / lib / rv64imafdc / lp64d / crt0.o \ 
   $ RV_GCC_LIB_PATH / riscv64-unknown-elf / 8.2.0 / rv64imafdc / lp64d / crtbegin.o \
   -lgcc-开始组-lc -lgloss-结束组-lgcc \ 
   $ RV_GCC_LIB_PATH / rv64imafdc / lp64d / crtend.o
   ...被截断...
 COLLECT_GCC_OPTIONS ='-O0''-g''-v''-march = rv64imafdc''-mabi = lp64d' 

我知道即使是缩写形式也很多,所以让我解释一下。 在第一行, gcc运行collect2程序,传递参数crt0.ocrtbegin.ocrtend.o-lgcc--start-group标志。 可以在这里找到collect2的描述:简而言之,collect2在启动时会组织各种初始化函数,从而一次或多次传递布局。

因此,GCC使用我们的代码编译了多个crt文件。 如您所料, crt表示“ C运行时”。 这里详细描述了每个crt用途,但是我们对crt0感兴趣,它做了一件重要的事情:

“此[crt0]对象应该包含_start字符,它指示程序的引导程序。”

“引导程序”的本质取决于平台,但是它通常涉及重要的任务,例如设置堆栈框架,传递命令行参数以及调用main 。 是的,我们终于找到了问题的答案: _start调用了我们的主要功能!

搜索我们的堆栈


我们解决了一个难题,但是这如何使我们更接近最初的目标-在gdb运行简单的C程序? 仍然需要解决几个问题:第一个问题与crt0如何配置堆栈有关。

如上所述, gcc默认为crt0 。 基于以下几个因素选择默认参数:

  • machine-vendor-operatingsystem的结构相对应的目标三元组 。 我们拥有riscv64-unknown-elf
  • 目标架构rv64imafdc
  • 目标ABI, lp64d

通常,一切都可以正常运行,但不适用于每个RISC-V处理器。 如前所述, crt0的任务之一是配置堆栈。 但是他不知道我们的CPU( -machine )的堆栈应该在哪里? 没有我们的帮助,他无法做到。

qemu-system-riscv64 -machine virt -m 128M -gdb tcp::1234 -kernel a.out我们使用了virt计算机。 幸运的是, qemu使将机器信息轻松转储到dtb转储(设备树blob)中。

 # Go to the ~/usys/riscv folder we created before and create a new dir # for our machine information. cd ~/usys/riscv && mkdir machines cd machines # Use qemu to dump info about the 'virt' machine in dtb (device tree blob) # format. # The data in this file represents hardware components of a given # machine / device / board. qemu-system-riscv64 -machine virt -machine dumpdtb=riscv64-virt.dtb 

Dtb数据很难读取,因为它基本上是二进制格式,但是有一个dtc (设备树编译器)命令行实用程序可以将文件转换为更具可读性的文件。

 # I'm running MacOS, so I use Homebrew to install this. If you're # running another OS you may need to do something else. brew install dtc # Convert our .dtb into a human-readable .dts (device tree source) file. dtc -I dtb -O dts -o riscv64-virt.dts riscv64-virt.dtb 

输出文件是riscv64-virt.dts ,在该文件中我们看到了许多关于virt的有趣信息:可用的处理器内核数量,各种外围设备(例如UART)的内存位置,内部内存(RAM)的位置。 堆栈应该在此内存中,因此请使用grep查找:

 grep memory riscv64-virt.dts -A 3 memory@80000000 { device_type = "memory"; reg = <0x00 0x80000000 0x00 0x8000000>; }; 

如您所见,此节点的“内存”指定为device_type 。 显然,我们找到了想要的东西。 通过reg = <...> ;的值reg = <...> ; 您可以确定存储库的起始位置及其长度。

devicetree规范中,我们看到reg语法是任意数量的对(base_address, length) 。 但是, reg内部有四个含义。 奇怪,两个值不足以容纳一个存储库吗?

再次,从devicetree规范(搜索reg属性)中我们发现,用于指定地址和长度的<u32>单元数由父节点(或节点本身)中的#address-cells#size-cells属性确定。 这些值未在我们的内存节点中指定,并且父内存节点只是文件的根。 让我们看一下这些值:

 head -n8 riscv64-virt.dts /dts-v1/; / { #address-cells = <0x02>; #size-cells = <0x02>; compatible = "riscv-virtio"; model = "riscv-virtio,qemu"; 

事实证明,地址和长度都需要两个32位值。 这意味着reg = <0x00 0x80000000 0x00 0x8000000>; 我们的内存开始 0x00 + 0x80000000 (0x80000000)并占用0x00 + 0x8000000 (0x8000000)字节,即,它以0x88000000结束,对应于128兆字节。

布局图


使用qemudtc我们在virt虚拟机中找到了RAM地址。 我们还知道, gcc默认情况下会组成crt0 ,而无需根据需要配置堆栈。 但是如何使用这些信息最终运行和调试程序?

由于crt0不适合我们,因此有一个显而易见的选择:编写您自己的代码,然后将其与在编译简单程序后获得的目标文件一起编写。 我们的crt0需要知道栈顶从哪里开始,以便正确地对其进行初始化。 我们可以crt00x80000000直接crt0crt0 ,但是考虑到将来可能需要的更改,这不是一个非常合适的解决方案。 如果我们想在仿真器中使用另一个具有不同特征的CPU,如sifive_e ,该怎么办?

幸运的是,我们不是第一个提出这个问题的人,而且已经存在一个好的解决方案。 GNU ld链接器使您可以定义 crt0可用的字符 。 我们可以定义适用于不同处理器的__stack_top符号。

与其从头开始编写自己的链接器文件,不如将默认脚本与ld并对其进行一些修改以支持其他字符。 什么是链接描述文件? 这是一个很好的描述

链接描述文件的主要目的是描述如何在输入和输出中匹配文件节,并控制输出文件的内存布局。

知道了这一点,让我们将默认的链接riscv64-unknown-elf-ld文件riscv64-unknown-elf-ld复制到一个新文件中:

 cd ~/usys/riscv # Make a new dir for custom linker scripts out RISC-V CPUs may require. mkdir ld && cd ld # Copy the default linker script into riscv64-virt.ld riscv64-unknown-elf-ld --verbose > riscv64-virt.ld 

该文件包含许多有趣的信息,远远超出了我们在本文中讨论的范围。 使用--Verbose详细输出包括有关ld版本,支持的体系结构等的信息。 知道这一切都是很好的,但是在链接描述文件中这种语法是不可接受的,因此请打开文本编辑器并从文件中删除所有多余的内容。

 病毒riscv64-virt.ld

 #删除上面的所有内容,包括=============
 GNU ld(GNU Binutils)2.32
  支持的仿真:
    elf64lriscv
    elf32lriscv
使用内部链接器脚本:
 ====================================================
 / * -z combreloc的脚本:组合和排序重定位节* /
 / *版权所有(C)2014-2019自由软件基金会,Inc.
   复制和分发此脚本(修改或不修改),
   允许在没有版权的情况下以任何媒体提供版权
   通知,此通知被保留。  * /
 OUTPUT_FORMAT(“ elf64-littleriscv”,“ elf64-littleriscv”,
	       “ elf64-littleriscv”)
 ...其余的链接描述文件... 

之后,运行MEMORY命令手动确定__stack_top位置。 找到以OUTPUT_ARCH(riscv)开头的行,该行应位于文件的顶部,并在其下方添加MEMORY命令:

 OUTPUT_ARCH(riscv) /* >>> Our addition. <<< */ MEMORY { /* qemu-system-risc64 virt machine */ RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 128M } /* >>> End of our addition. <<< */ ENTRY(_start) 

我们创建了一个称为RAM的存储块,允许对其进行读( r ),写( w )和存储可执行代码( x )。

太好了,我们已经定义了一个匹配virt RISC-V机器规格的内存布局。 现在您可以使用它了。 我们想将堆栈放入内存。

您需要定义__stack_top字符。 在文本编辑器中打开链接描述文件( riscv64-virt.ld )并添加几行:

 SECTIONS { /* Read-only sections, merged into text segment: */ PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x10000)); . = SEGMENT_START("text-segment", 0x10000) + SIZEOF_HEADERS; /* >>> Our addition. <<< */ PROVIDE(__stack_top = ORIGIN(RAM) + LENGTH(RAM)); /* >>> End of our addition. <<< */ .interp : { *(.interp) } .note.gnu.build-id : { *(.note.gnu.build-id) } 

如您所见,我们使用__stack_top 命令定义__stack_top 。 可以从与此脚本关联的任何程序中访问该符号(假设该程序本身不会确定名称为__stack_top东西)。 将__stack_top设置为ORIGIN(RAM) 。 我们知道此值为0x80000000加上LENGTH(RAM) ,即128兆字节( 0x8000000字节)。 这意味着我们的__stack_top设置为0x88000000

为简便起见,我不会在此处列出整个链接器文件;您可以在此处查看

别说了 锤击时间! 运行时!


现在我们拥有创建自己的C运行时所需的一切,实际上,这是一个相当简单的任务,这是整个crt0.s文件:

 .section .init, "ax" .global _start _start: .cfi_startproc .cfi_undefined ra .option push .option norelax la gp, __global_pointer$ .option pop la sp, __stack_top add s0, sp, zero jal zero, main .cfi_endproc .end 

立即吸引了大量以句点开头的行。 这是用于汇编程序的文件。 带点的线称为汇编程序伪指令 :它们为汇编程序提供信息。 这不是可执行代码,例如RISC-V汇编程序指令(例如jaladd

让我们逐行浏览文件。 我们将使用各种标准RISC-V寄存器,因此请查看此表该表涵盖了所有寄存器及其用途。

 .section .init, "ax" 

GNU汇编程序“ as”手册中所述 ,此行告诉汇编程序将以下代码插入.init节,该节已分配( a )和可执行文件( x )。 本节是在操作系统中运行代码的另一个常见约定 。 我们在没有操作系统的纯硬件上工作,因此在我们的情况下,这样的说明可能不是绝对必要的,但是在任何情况下,这都是一个好习惯。

 .global _start _start: 

.global使ld可以使用以下字符。 否则,链接将不起作用,因为链接描述文件中的ENTRY(_start)指向_start符号,作为可执行文件的入口。 下一行告诉汇编程序我们正在开始_start字符的定义。

 _start: .cfi_startproc .cfi_undefined ra ...other stuff... .cfi_endproc 

这些.cfi指令会通知您有关框架的结构以及如何处理它。 .cfi_startproc.cfi_endproc函数的开始和结束, .cfi_undefined ra告诉汇编程序,在_start之前, 不得将 ra寄存器恢复为包含的任何值。

 .option push .option norelax la gp, __global_pointer$ .option pop 

当您需要应用一组特定的选项时,这些.option伪指令根据代码更改汇编程序的行为。 这是为什么在.option中使用.option很重要详细说明:

...由于我们可能会将序列的寻址放宽到相对于GP较短的序列,因此GP的初始加载不应受到削弱,应该是这样的:

 .option push .option norelax la gp, __global_pointer$ .option pop 

这样,在放松之后,您将获得以下代码:

 auipc gp, %pcrel_hi(__global_pointer$) addi gp, gp, %pcrel_lo(__global_pointer$) 

而不是简单的:

 addi gp, gp, 0 

现在,我们的crt0.s的最后一部分:

 _start: ...other stuff... la sp, __stack_top add s0, sp, zero jal zero, main .cfi_endproc .end 

在这里,我们终于可以使用我们努力创建的__stack_top符号。 伪指令 la (加载地址)将__stack_top值加载到sp寄存器(堆栈指针)中,并将其设置为在程序的其余部分中使用。

然后add s0, sp, zero ,将寄存器spzero的值相加(实际上是硬引用0的寄存器x0 ),并将结果放入寄存器s0 。 这是一个特殊的寄存器 ,在几个方面都不常见。 首先,它是一个“永久寄存器”,即在函数调用时保存。 其次, s0有时充当帧指针,这给每个函数调用一个小的空间,用于在堆栈中存储传递给该函数的参数。 函数调用如何与堆栈指针和框架指针一起工作是一个非常有趣的主题,您可以轻松地专门写一篇单独的文章,但是就目前而言,仅知道在我们的运行时中,初始化框架指针s0很重要。

接下来,我们看到jal zero, main声明。 在这里, jal表示跳转和链接。 该指令期望操作数的形式为jal rd (destination register), offset_address 。 在功能上, jal将下一条指令的值( pc寄存器加4)写入rd ,然后将pc寄存器设置为当前pc值加上带符号扩展名的偏移地址,从而有效地“调用”该地址。

如上所述, x0与字面值0紧密绑定,对其进行写入是没有用的。 , zero , RISC-V x0 . offset_address . , ?

jal zero, offset_address . , , . ISA, . , jal unconditional jump , RISC-V jal , jal zero, main .

RISC-V , . , . , j offset_address RISC-V jal zero, offset_address . . RISC-V ( 2.2) .

 _start: ...other stuff... jal zero, main .cfi_endproc .end 

.end , .

, -


C RISC-V, . qemu dtc virt RISC-V. riscv64-unknown-elf-ld , __stack_top . crt0.s , , , main . GDB.

, C:

 cat add.c int main() { int a = 4; int b = 12; while (1) { int c = a + b; } return 0; } 

:

 riscv64-unknown-elf-gcc -g -ffreestanding -O0 -Wl,--gc-sections -nostartfiles -nostdlib -nodefaultlibs -Wl,-T,riscv64-virt.ld crt0.s add.c 

, , , .

-ffreestanding , , . ( ), , .

-Wl — ( ld ). --gc-sections « », ld . -nostartfiles , -nostdlib -nodefaultlibs (, crt0 ), stdlib . crt0 , , .

-T , riscv64-virt.ld . , , , : crt0.s add.c . , a.out .

qemu :

 # -S freezes execution of our executable (-kernel) until we explicitly tell # it to start with a 'continue' or 'c' from our gdb client qemu-system-riscv64 -machine virt -m 128M -gdb tcp::1234 -S -kernel a.out 

gdb , a.out , :

 riscv64-unknown-elf-gdb --tui a.out GNU gdb (GDB) 8.2.90.20190228-git Copyright (C) 2019 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "--host=x86_64-apple-darwin17.7.0 --target=riscv64-unknown-elf". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from a.out... (gdb) 

gdb gdb , qemu :

 (gdb) target remote :1234 │ Remote debugging using :1234 

main:

 (gdb) b main Breakpoint 1 at 0x8000001e: file add.c, line 2. 

:

 (gdb) c Continuing. Breakpoint 1, main () at add.c:2 

, 2! , - L , PC: L2 , PC:0x8000001e . , :



gdb : -s , info all-registers . . … , , !

接下来是什么?


, , ! , , . , . jal , , , add.c - RISC-V. - , - , .

! , !

选配


, « : main()» CppCon2018. , . , !

Source: https://habr.com/ru/post/zh-CN454208/


All Articles