RISC-V 汇编语言

news2024/12/23 14:03:45

安装RISCV工具链

riscv-gnu-toolchain工具链和模拟器安装记录 - 知乎 (zhihu.com)

riscv-gnu-toolchain工具链分elf-gcc、linux-gnu-gcc两个版本,以及对应的32位和64位版本。两个版本的主要区别是:

  • riscv32-unknown-elf-gcc、riscv64-unknown-elf-gcc使用的是riscv-newlib库(面向嵌入式的C库),只支持静态链接,不支持动态链接。
  • riscv32-unknown-linux-gnu-gcc、riscv64-unknown-linux-gnu-gcc使用的是glibc标准库,支持动态链接。

最终选用的是riscv64-unknown-linux-gnu-gcc,支持动态链接

函数调用约定

x86

  • 调用约定
  • 常见的调用约定包括 cdeclstdcallfastcall
  • 不同的调用约定通常是为了满足特定编程语言或平台的需求。
  • cdecl(C Declaration):调用者清理堆栈,支持可变参数,C语言标准推荐使用
  • stdcall:被调用者清理堆栈,不支持可变参数,通常用于 Windows API
  • fastcall:使用寄存器传递前两个参数(ECX 和 EDX),其余参数通过堆栈传递,适用于函数频繁调用的场景。

平台与工具链
不同的编译器和平台可能会选择不同的调用约定。

例如,Microsoft 的 Visual C++ 编译器通常支持 stdcall 和 fastcall,

而 GCC 支持 cdecl 和其他约定。

  • 参数传递:参数从右到左入栈。
  • 返回值:返回值通常通过 EAX 寄存器返回。
  • 通用寄存器:8 个(EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP)
  • 指令指针寄存器:1 个(EIP)
  • 段寄存器:6 个(CS, DS, SS, ES, FS, GS)
  • 总数:大约 15 个(包含所有类型)

x64

  • 调用约定
  • 常用的调用约定为 Microsoft 和 System V(Linux 和 macOS)。
  • Microsoft x64(Window操作系统,常与WindowsAPI 交互)
    • 前四个整型或指针参数通过寄存器(RCX、RDX、R8、R9)传递,其余通过堆栈。
    • 返回值通过 RAX 寄存器返回。
  • System V x64( Linux /Unix操作系统 , 常与 POSIX 标准库和开源项目兼容。 )
    • 前六个整型或指针参数通过寄存器(RDI、RSI、RDX、RCX、R8、R9)传递,其余通过堆栈。
    • 返回值通过 RAX 寄存器返回。
  • 参数传递:同样是从右到左入栈。
  • 通用寄存器:16 个(RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, R8, R9, R10, R11, R12, R13, R14, R15)
  • 指令指针寄存器:1 个(RIP)
  • 总数:17 个(包含所有类型)

RV32(RV64类似,但支持更大的寄存器范围)

  • 调用约定
  • 使用 RISC-V 的 ABI,函数参数通过寄存器和堆栈传递。
  • 参数传递
  • 前八个参数通过寄存器(a0 到 a7)传递。
  • 如果参数超过八个,其余参数通过堆栈传递。
  • 返回值
  • 返回值通过 a0 寄存器(即 x10)返回。

调用约定
函数调用过程通常分为 6 个阶段 [Patterson and Hennessy 2021]:

  1. 将参数存放到函数可访问的位置;
  2. 跳转到函数入口(使用 RV32I 的 jal 指令);
  3. 获取函数所需局部存储资源,按需保存寄存器;
  4. 执行函数功能;
  5. 将返回值存放到调用者可访问的位置,恢复寄存器,释放局部存储资源;
  6. 由于程序可从多处调用函数,故需将控制权返回到调用点(使用 ret 指令)

为提升性能,应尽量将变量存放在寄存器而不是内存中,但同时也要避免因保存

和恢复寄存器(函数调用和上下文切换)而频繁访问内存。

RISCV架构优势在于

1.足量的寄存器

2.可以减少保存和恢复寄存器的次数

寄存器的分类

1.临时寄存器:不保证其值在函数调用前后的一致性

2.保存寄存器:保证其值在函数调用前后的一致性

叶子函数

不再调用其他函数的函数称为叶子函数。

当一个叶子函数只有少量参数和局部变量时,可将其分配到寄存器, 无需分配到内存。大部分函数调用均如此,此时程序无需将寄存器保存到内存

RISCV寄存器

比较特殊的几个寄存器

  1. 裸机环境
    在裸机(bare-metal)编程中,如果程序没有操作系统,tp 通常不会被使用。
    如果程序需要自己实现多线程,则可以定义一个线程控制块,并通过软件设置 tp 指向相应的结构。

在函数入口处和出口处标准的RV32I代码


entry_label:
addi sp,sp,-framesize # 调整栈指针(sp 寄存器)来分配栈帧
sw ra,framesize-4(sp) # 保存返回地址(ra 寄存器)
# 按需保存其他寄存器
... # 函数体

# 按需恢复其他寄存器
lw ra,framesize-4(sp) # 恢复返回地址寄存器
addi sp,sp, framesize # 释放栈帧空间
ret # 返回调用点

汇编器

汇编器的作用

1.面向处理器:生成的目标代码(处理器可以理解的指令)

2.面向程序员和编译器开发者:伪指令(常规指令的巧妙特例)

RISCV中的伪指令分为两类

1.依赖恒为0的x0寄存器(硬连线为0)——————>极大简化了RISCV指令集

电气连接:在电路设计中,硬连线意味着某个信号线直接连接到地(0V),从而使得该信号始终保持在低电平状态(即逻辑 0)。

2.其他

伪指令

1.依赖恒为0的x0寄存器的伪指令(32条)

set not equal to zero

set less than zero

set greater than zero

将rs与x0寄存器的值(0)进行条件比较,满足比较条件将rd置为1,否则为0

看大于0时置位,实则是用slt指令进行比较的,无sgt指令

branch equal to zero

以第一条为例,比较rs寄存器的值是否为0,为0则跳转到当前指令相对偏移offset地址处

jump and link 无条件跳转

1.jal x0, offset 会直接跳转到目标地址

适用于不需要保存返回地址的跳转,offset:是一个相对地址偏移量,指向要跳转到的目标指令地址

(正常情况下在进行函数调用时,需要将调用函数处的下一条指令的地址保存在寄存器中,此处寄存器为x0,硬连线为0, 这种跳转适用于不需要返回的场景,如错误处理或实现循环)

2.jalr x0, rs, 0 会直接跳转到 rs 中存储的地址,

3. 同上,唯一区别是返回地址寄存器x1

在 RISC-V 架构中,x1 寄存器通常被称为 ra(return address)寄存器,专门用于存储返回地址。 此处代表从子过程返回

什么是尾调用?

以C源代码为例,将return func();成为尾调用

用jmp 跳转指令替换掉call+ret组合的指令成为尾调用优化

尾调用优化,用jmp 跳转指令替换掉call+ret组合的指令成为尾调用优化,减少栈帧的维护

尾调用优化可以减少函数中不必要的 调用流程

时间上:更少更快的指令

空间上:更少的栈帧,不再容易产生overflow的问题,减少内存中需要维护的栈帧数量

编译器/解释器支持尾调用优化

clang 、gcc对尾调用优化都有很好的支持

javaScript在Webkit引擎上可以进行适当的尾调用优化

不支持尾调用优化

两条指令如何理解呢?在 RISC-V 中,auipcjalr 指令可以用来实现远距离的尾调用(tail call)操作。让我们逐条分析这两条指令的功能以及它们在尾调用中的角色。

AUIPCJALR 里的 12 位立即数的组合,能够转移控制到任意的 32 位 PC 相对地址

AUIPC 先把 32位立即数的高 20 位,补上低 12 位全 0跟 PC 相加,把结果存到某个寄存器里

JALR 再把这个寄存器和 32 位立即数的低 12 位相加,跳转到相加得到的目标地址。

也就是实现了 32 位立即数加上 PC的目标地址

三个相关的CSR寄存器

1.instret:这是一个 CSR,表示已提交指令的数量。它是一个 64 位的寄存器(在 RV64 中),可以分为高 32 位和低 32 位,分别是 instret[63:32]instret[31:0],通常通过 instret[h] 访问高 32 位,通过 instret 访问低 32 位。

2.cycle:这是一个 CSR,表示 CPU 时钟周期数。它也是一个 64 位寄存器,可以分为高 32 位和低 32 位,分别是 cycle[63:32]cycle[31:0]。通过 cycle[h] 访问高 32 位。

3.time:这是一个 CSR,表示当前实时时间。与前两者一样,它是一个 64 位寄存器,可以分为高 32 位和低 32 位,分别是 time[63:32]time[31:0]。通过 time[h] 访问高 32 位。

这三条指令的主要作用是读取 RISC-V 处理器的性能计数器,便于进行性能分析和监控。通过这些寄存器,开发者可以获取有关 CPU 执行的指令数量、时钟周期和当前时间的信息。这对于优化代码和评估系统性能非常重要

CSR置位详细解释:

  • 操作csr = csr | rs
  • 目的
    • 通过将 rs 中为 1 的位与 csr 中对应的位进行按位操作,来设置 csr 中的某些位。
    • csrrs 指令用于启用某些功能或状态,例如启用特定的中断或设置状态位

CSR清位详细解释:

  • 操作csr = csr & ~rs
  • 目标寄存器x0 是目标寄存器,但由于它是硬连线为 0,所以指令执行后不会保存 csr 的原始值。
  • 功能:该指令会从 csr 中清除与 rs 中为 1 的位对应的位。这意味着 csr 中被清零的位是根据 rs 中的位设置决定的

浮点的舍入模式

FRM 寄存器值与舍入模式对应关系

FRM 值

舍入模式

说明

0

Round to Nearest (RN)

最近舍入:向最接近的可表示值舍入。如果恰好在两个值之间,则向偶数舍入(即取最接近的偶数)。

1

Round Toward Zero (RZ)

向零舍入:直接截断小数部分,向零方向舍入。

2

Round Toward Positive Infinity (RP)

向上舍入:总是向正无穷舍入,适用于希望结果偏大时的场景。

3

Round Toward Negative Infinity (RN)

向下舍入:总是向负无穷舍入,适用于希望结果偏小时的场景。

浮点异常标志

浮点异常标志(FFLAGS)

标志位

名称

说明

0

Invalid Operation (IV)

无效操作:指示执行了无效的浮点操作,例如除以零或无效的浮点数。

1

Division by Zero (DZ)

除以零:指示发生了除以零的操作。

2

Overflow (OV)

溢出:指示浮点运算结果超出了可以表示的范围。

3

Underflow (UF)

下溢:指示浮点运算结果过小,低于可表示的最小正数(非零)。

4

Inexact (NX)

不精确:指示运算结果不是精确的结果(例如舍入发生)。

RV32I 中,那些读取 64 位计数器的指令默认读取低 32 位,可通过 “h” 结尾的指令

读取高 32 位。

在 RV32I 架构中,64 位计数器通常指的是用来记录处理器执行周期、已执行指令数量或时间的特定寄存器。例如:

  1. Cycle Counter:用于跟踪 CPU 时钟周期的数量,帮助进行性能分析。
  2. Instruction Retired Counter:记录已经执行的指令数量。

这些计数器通常被实现为 64 位,以便能够记录长时间运行程序所产生的计数值。但在 RV32I 的 32 位环境中,默认读取操作只涉及到这类寄存器的低 32 位部分。要读取高 32 位部分,则需要使用带有 “h” 后缀的特殊指令。这种设计允许处理器在性能监控方面提供更大的灵活性和更精确的计数。

2.其他伪指令(28条)

fence iorw, iorw 的作用是确保在执行此指令之前的所有 I/O 操作和内存读写操作都已经完成,且在此指令之后的 I/O 和内存操作在屏障之前的操作完成后才会执行。这种屏障对于多处理器或多线程环境尤为重要,可以防止因操作顺序不当而导致的竞态条件或数据不一致性。

程序实例

1.源代码
#include <stdio.h>
int main()
{
    printf("Hello, %s\n", "world");
    return 0;
}
2.编译生成汇编程序Hello.s
riscv64-unknown-linux-gnu-gcc -S Hello.c -o Hello.s
  • -S 选项告诉编译器将 C 程序编译为汇编代码,输出文件为 Hello.s
  • -o Hello.s 指定输出文件名。
	.file	"Hello.c"
	.option nopic
	.attribute arch, "rv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0"
	.attribute unaligned_access, 0
	.attribute stack_align, 16
	.text
	.section	.rodata
	.align	3
.LC0:
	.string	"world"
	.align	3
.LC1:
	.string	"Hello, %s\n"
	.text
	.align	1
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	addi	sp,sp,-16
	.cfi_def_cfa_offset 16
	sd	ra,8(sp)
	sd	s0,0(sp)
	.cfi_offset 1, -8
	.cfi_offset 8, -16
	addi	s0,sp,16
	.cfi_def_cfa 8, 0
	lui	a5,%hi(.LC0)
	addi	a1,a5,%lo(.LC0)
	lui	a5,%hi(.LC1)
	addi	a0,a5,%lo(.LC1)
	call	printf
	li	a5,0
	mv	a0,a5
	ld	ra,8(sp)
	.cfi_restore 1
	ld	s0,0(sp)
	.cfi_restore 8
	.cfi_def_cfa 2, 16
	addi	sp,sp,16
	.cfi_def_cfa_offset 0
	jr	ra
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: () 13.2.0"
	.section	.note.GNU-stack,"",@progbits

汇编指示符:
形式:以英文句号开头

作用:通知汇编器在何处放置代码和数据,指定程序中使用的代码和数据常量

在以上程序中可能出现的汇编指示符,如下所示:

常见的汇编指示符

3.汇编生成目标程序Hello.o
riscv64-unknown-linux-gnu-gcc -c Hello.s -o Hello.o

-c 选项告诉编译器只进行编译而不进行链接,输出一个目标文件 Hello.o

riscv64-unknown-linux-gnu-objdump -d Hello.o

汇编器生成如下所示的 ELF(Executable and Linkable Format,可执行可链接格式) 标准格式目标文件

Hello.o:     file format elf64-littleriscv


Disassembly of section .text:

0000000000000000 <main>:
   0:	1141                	addi	sp,sp,-16
   2:	e406                	sd	ra,8(sp)
   4:	e022                	sd	s0,0(sp)
   6:	0800                	addi	s0,sp,16
   8:	000007b7          	lui	a5,0x0
   c:	00078593          	mv	a1,a5
  10:	000007b7          	lui	a5,0x0
  14:	00078513          	mv	a0,a5
  18:	00000097          	auipc	ra,0x0
  1c:	000080e7          	jalr	ra # 18 <main+0x18>
  #如上六条指令的地址字段为0,后续由链接器填充
  20:	4781                	li	a5,0
  22:	853e                	mv	a0,a5
  24:	60a2                	ld	ra,8(sp)
  26:	6402                	ld	s0,0(sp)
  28:	0141                	addi	sp,sp,16
  2a:	8082                	ret

除指令外,每个目标文件还包含一张符号表,用于记录程序中所有需要在链接过程中确定地址的符号

其中包含数据符号和代码符号。

riscv64-unknown-linux-gnu-objdump -t Hello.o
Hello.o:     file format elf64-littleriscv

SYMBOL TABLE:
0000000000000000 l    df *ABS*	0000000000000000 Hello.c
0000000000000000 l    d  .text	0000000000000000 .text
0000000000000000 l    d  .data	0000000000000000 .data
0000000000000000 l    d  .bss	0000000000000000 .bss
0000000000000000 l    d  .rodata	0000000000000000 .rodata
0000000000000000 l    d  .note.GNU-stack	0000000000000000 .note.GNU-stack
0000000000000000 l    d  .comment	0000000000000000 .comment
0000000000000000 l    d  .eh_frame	0000000000000000 .eh_frame
0000000000000000 l    d  .riscv.attributes	0000000000000000 .riscv.attributes
#代码符号
0000000000000000 g     F .text	000000000000002c main 
0000000000000000         *UND*	0000000000000000 printf

数据符号(如字符串常量):常量字符串(如 "Hello, %s\n""world")被存储在 .rodata 段中,通常没有单独的符号名,符号表只会标记这些段存在,而不会为每个常量字符串分配单独的符号

oslab@oslab-virtual-machine:/opt/riscv/bin$ riscv64-unknown-linux-gnu-objdump -s -j .rodata Hello.o

Hello.o:     file format elf64-littleriscv

Contents of section .rodata:
 0000 776f726c 64000000 48656c6c 6f2c2025  world...Hello, %
 0010 730a00  

生成PIC位置无关的目标文件

使用-fPIC编译选项

riscv64-unknown-linux-gnu-gcc -fPIC -c Hello.c -o Hello_pic.o

riscv64-unknown-linux-gnu-objdump -d Hello_pic.o

oslab@oslab-virtual-machine:/opt/riscv/bin$ riscv64-unknown-linux-gnu-objdump -d Hello_pic.o

Hello_pic.o:     file format elf64-littleriscv


Disassembly of section .text:

0000000000000000 <main>:
   0:	1141                	addi	sp,sp,-16
   2:	e406                	sd	ra,8(sp)
   4:	e022                	sd	s0,0(sp)
   6:	0800                	addi	s0,sp,16
   8:	00000597          	auipc	a1,0x0
   c:	00058593          	mv	a1,a1
  10:	00000517          	auipc	a0,0x0
  14:	00050513          	mv	a0,a0
  18:	00000097          	auipc	ra,0x0
  1c:	000080e7          	jalr	ra # 18 <main+0x18>
  20:	4781                	li	a5,0
  22:	853e                	mv	a0,a5
  24:	60a2                	ld	ra,8(sp)
  26:	6402                	ld	s0,0(sp)
  28:	0141                	addi	sp,sp,16
  2a:	8082                	ret

riscv64-unknown-linux-gnu-gcc -pie -o Hello_pic Hello_pic.o

4.链接生成可执行文件Hello.out
riscv64-unknown-linux-gnu-gcc Hello.o -o Hello.out
riscv64-unknown-linux-gnu-objdump -d Hello.out
Hello.out:     file format elf64-littleriscv


Disassembly of section .plt:

0000000000010430 <_PROCEDURE_LINKAGE_TABLE_>:
   10430:	97 23 00 00 33 03 c3 41 03 be 03 bc 13 03 43 fd     .#..3..A......C.
   10440:	93 82 03 bc 13 53 13 00 83 b2 82 00 67 00 0e 00     .....S......g...

0000000000010450 <__libc_start_main@plt>:
   10450:	00002e17          	auipc	t3,0x2
   10454:	bb0e3e03          	ld	t3,-1104(t3) # 12000 <__libc_start_main@GLIBC_2.34>
   10458:	000e0367          	jalr	t1,t3
   1045c:	00000013          	nop

0000000000010460 <printf@plt>:
   10460:	00002e17          	auipc	t3,0x2
   10464:	ba8e3e03          	ld	t3,-1112(t3) # 12008 <printf@GLIBC_2.27>
   10468:	000e0367          	jalr	t1,t3
   1046c:	00000013          	nop

Disassembly of section .text:

0000000000010470 <_start>:
   10470:	024000ef          	jal	10494 <load_gp>
   10474:	87aa                	mv	a5,a0
   10476:	00000517          	auipc	a0,0x0
   1047a:	01c50513          	addi	a0,a0,28 # 10492 <__wrap_main>
   1047e:	6582                	ld	a1,0(sp)
   10480:	0030                	addi	a2,sp,8
   10482:	ff017113          	andi	sp,sp,-16
   10486:	4681                	li	a3,0
   10488:	4701                	li	a4,0
   1048a:	880a                	mv	a6,sp
   1048c:	fc5ff0ef          	jal	10450 <__libc_start_main@plt>
   10490:	9002                	ebreak

0000000000010492 <__wrap_main>:
   10492:	a89d                	j	10508 <main>

0000000000010494 <load_gp>:
   10494:	00002197          	auipc	gp,0x2
   10498:	37c18193          	addi	gp,gp,892 # 12810 <__global_pointer$>
   1049c:	8082                	ret
	...

00000000000104a0 <deregister_tm_clones>:
   104a0:	6549                	lui	a0,0x12
   104a2:	6749                	lui	a4,0x12
   104a4:	01050793          	addi	a5,a0,16 # 12010 <__TMC_END__>
   104a8:	01070713          	addi	a4,a4,16 # 12010 <__TMC_END__>
   104ac:	00f70863          	beq	a4,a5,104bc <deregister_tm_clones+0x1c>
   104b0:	00000793          	li	a5,0
   104b4:	c781                	beqz	a5,104bc <deregister_tm_clones+0x1c>
   104b6:	01050513          	addi	a0,a0,16
   104ba:	8782                	jr	a5
   104bc:	8082                	ret

00000000000104be <register_tm_clones>:
   104be:	6549                	lui	a0,0x12
   104c0:	01050793          	addi	a5,a0,16 # 12010 <__TMC_END__>
   104c4:	6749                	lui	a4,0x12
   104c6:	01070593          	addi	a1,a4,16 # 12010 <__TMC_END__>
   104ca:	8d9d                	sub	a1,a1,a5
   104cc:	4035d793          	srai	a5,a1,0x3
   104d0:	91fd                	srli	a1,a1,0x3f
   104d2:	95be                	add	a1,a1,a5
   104d4:	8585                	srai	a1,a1,0x1
   104d6:	c599                	beqz	a1,104e4 <register_tm_clones+0x26>
   104d8:	00000793          	li	a5,0
   104dc:	c781                	beqz	a5,104e4 <register_tm_clones+0x26>
   104de:	01050513          	addi	a0,a0,16
   104e2:	8782                	jr	a5
   104e4:	8082                	ret

00000000000104e6 <__do_global_dtors_aux>:
   104e6:	1141                	addi	sp,sp,-16
   104e8:	e022                	sd	s0,0(sp)
   104ea:	6449                	lui	s0,0x12
   104ec:	01844783          	lbu	a5,24(s0) # 12018 <completed.0>
   104f0:	e406                	sd	ra,8(sp)
   104f2:	e791                	bnez	a5,104fe <__do_global_dtors_aux+0x18>
   104f4:	fadff0ef          	jal	104a0 <deregister_tm_clones>
   104f8:	4785                	li	a5,1
   104fa:	00f40c23          	sb	a5,24(s0)
   104fe:	60a2                	ld	ra,8(sp)
   10500:	6402                	ld	s0,0(sp)
   10502:	0141                	addi	sp,sp,16
   10504:	8082                	ret

0000000000010506 <frame_dummy>:
   10506:	bf65                	j	104be <register_tm_clones>

0000000000010508 <main>:
   10508:	1141                	addi	sp,sp,-16
   1050a:	e406                	sd	ra,8(sp)
   1050c:	e022                	sd	s0,0(sp)
   1050e:	0800                	addi	s0,sp,16


在第一段代码中,a1 和 a0 寄存器的值是通过 lui 和 addi 指令计算的,
偏移量是常量(1336 和 1344),计算的是某个绝对地址附近的值。
   
   10510:	67c1                	lui	a5,0x10
   10512:	53878593          	addi	a1,a5,1336 # 10538 <_IO_stdin_used+0x8>
   10516:	67c1                	lui	a5,0x10
   10518:	54078513          	addi	a0,a5,1344 # 10540 <_IO_stdin_used+0x10>
   1051c:	f45ff0ef          	jal	10460 <printf@plt>


在第二段位置无关(PIC)代码中,a1 和 a0 寄存器的值是通过 auipc 和 addi 来构造的,
使用的是相对地址(相对于当前代码位置的偏移)
这意味着在生成的位置无关代码中,程序的地址依赖于当前指令的位置,
而不是固定的常量地址。

 690:	00000597          	auipc	a1,0x0
 694:	02858593          	addi	a1,a1,40 # 6b8 <_IO_stdin_used+0x8>
 698:	00000517          	auipc	a0,0x0
 69c:	02850513          	addi	a0,a0,40 # 6c0 <_IO_stdin_used+0x10>
 6a0:	f21ff0ef          	jal	5c0 <printf@plt>

   10520:	4781                	li	a5,0
   10522:	853e                	mv	a0,a5
   10524:	60a2                	ld	ra,8(sp)
   10526:	6402                	ld	s0,0(sp)
   10528:	0141                	addi	sp,sp,16
   1052a:	8082                	ret




链接器

动态链接

1.plt表和got表

2.延迟绑定

在RISCV中Linux动态链接和延迟绑定

#include <stdio.h>
void print_banner()
{
    printf("Welcome to World of PLT and GOT\n");
}
int main(void)
{
    print_banner();
    return 0;
}

依次用以下的编译命令,现在有原有的 test.c 还有个 test.o 以及可执行文件 test

riscv64-unknown-linux-gnu-gcc -Wall -g -o test.o -c test.c
riscv64-unknown-linux-gnu-gcc -o test test.o

执行RISCV程序test

oslab@oslab-virtual-machine:/opt/riscv/bin/Linker/dynamic$ qemu-riscv64 test
Welcome to World of PLT and GOT

查看反汇编

riscv64-unknown-linux-gnu-objdump -d test.o
test.o:     file format elf64-littleriscv


Disassembly of section .text:

0000000000000000 <print_banner>:
   0:	1141                	addi	sp,sp,-16
   2:	e406                	sd	ra,8(sp)
   4:	e022                	sd	s0,0(sp)
   6:	0800                	addi	s0,sp,16
   8:	000007b7          	lui	a5,0x0
   c:	00078513          	mv	a0,a5
  10:	00000097          	auipc	ra,0x0
  14:	000080e7          	jalr	ra # 10 <print_banner+0x10>
  18:	0001                	nop
  1a:	60a2                	ld	ra,8(sp)
  1c:	6402                	ld	s0,0(sp)
  1e:	0141                	addi	sp,sp,16
  20:	8082                	ret

0000000000000022 <main>:
  22:	1141                	addi	sp,sp,-16
  24:	e406                	sd	ra,8(sp)
  26:	e022                	sd	s0,0(sp)
  28:	0800                	addi	s0,sp,16
  2a:	00000097          	auipc	ra,0x0
  2e:	000080e7          	jalr	ra # 2a <main+0x8>
  32:	4781                	li	a5,0
  34:	853e                	mv	a0,a5
  36:	60a2                	ld	ra,8(sp)
  38:	6402                	ld	s0,0(sp)
  3a:	0141                	addi	sp,sp,16
  3c:	8082                	ret

riscv64-unknown-linux-gnu-objdump -d test > test.asm

Disassembly of section .plt:

0000000000010430 <_PROCEDURE_LINKAGE_TABLE_>:
   10430:	97 23 00 00 33 03 c3 41 03 be 03 bc 13 03 43 fd     .#..3..A......C.
   10440:	93 82 03 bc 13 53 13 00 83 b2 82 00 67 00 0e 00     .....S......g...

0000000000010450 <__libc_start_main@plt>:
   10450:	00002e17          	auipc	t3,0x2
   10454:	bb0e3e03          	ld	t3,-1104(t3) # 12000 <__libc_start_main@GLIBC_2.34>
   10458:	000e0367          	jalr	t1,t3
   1045c:	00000013          	nop

0000000000010460 <puts@plt>:
   10460:	00002e17          	auipc	t3,0x2
   10464:	ba8e3e03          	ld	t3,-1112(t3) # 12008 <puts@GLIBC_2.27>
   10468:	000e0367          	jalr	t1,t3
   1046c:	00000013          	nop

可以看到,除了第一个plt表项外,每个plt 表都是跳转到对应的 got 表项

对test使用qemu-riscv64和riscv64-unknown-linux-gnu-gdb进行调试

qemu-riscv64 -singlestep -g 1234 test
oslab@oslab-virtual-machine:/opt/riscv/bin/Linker/dynamic$ 
riscv64-unknown-linux-gnu-gdb ./test
GNU gdb (GDB) 14.2
Copyright (C) 2023 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-pc-linux-gnu --target=riscv64-unknown-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://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 ./test...
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
Reading symbols from /opt/riscv/sysroot/lib/ld-linux-riscv64-lp64d.so.1...
(No debugging symbols found in /opt/riscv/sysroot/lib/ld-linux-riscv64-lp64d.so.1)
0x00000040008112e2 in _start () from /opt/riscv/sysroot/lib/ld-linux-riscv64-lp64d.so.1
(gdb) b main
Breakpoint 1 at 0x1052c: file test.c, line 8.
(gdb) c
Continuing.

之后再test.asm中

得到got表项的地址,在gdb中查看

(gdb) x/x 0x12008
0x12008 <puts@got.plt>:	0x00010430

可以看到此处的puts的got表项的地址就是plt表的0x10430

接着调试执行

(gdb) disas
Dump of assembler code for function puts@plt:
   0x0000000000010460 <+0>:	auipc	t3,0x2
   0x0000000000010464 <+4>:	ld	t3,-1112(t3) # 0x12008 <puts@got.plt>
=> 0x0000000000010468 <+8>:	jalr	t1,t3
   0x000000000001046c <+12>:	nop
End of assembler dump.
(gdb) info r t3
t3             0x10430	66608
(gdb) si
0x0000000000010430 in _PROCEDURE_LINKAGE_TABLE_ ()
(gdb) info r t3
t3             0x10430	66608
(gdb) disas
Dump of assembler code for function _PROCEDURE_LINKAGE_TABLE_:
=> 0x0000000000010430 <+0>:	auipc	t2,0x2
   0x0000000000010434 <+4>:	sub	t1,t1,t3
   0x0000000000010438 <+8>:	ld	t3,-1088(t2) # 0x11ff0
   0x000000000001043c <+12>:	addi	t1,t1,-44
   0x0000000000010440 <+16>:	addi	t0,t2,-1088
   0x0000000000010444 <+20>:	srli	t1,t1,0x1
   0x0000000000010448 <+24>:	ld	t0,8(t0)
   0x000000000001044c <+28>:	jr	t3
End of assembler dump. 

最终要跳转到t3处的值,也就是_dl_runtime_resolve函数

0x000000400080d7b4 in _dl_runtime_resolve ()
   from /opt/riscv/sysroot/lib/ld-linux-riscv64-lp64d.so.1
(gdb) disas
Dump of assembler code for function _dl_runtime_resolve:
=> 0x000000400080d7b4 <+0>:	addi	sp,sp,-144
   0x000000400080d7b6 <+2>:	sd	ra,72(sp)
   0x000000400080d7b8 <+4>:	sd	a0,8(sp)
   0x000000400080d7ba <+6>:	sd	a1,16(sp)
   0x000000400080d7bc <+8>:	sd	a2,24(sp)
   0x000000400080d7be <+10>:	sd	a3,32(sp)
   0x000000400080d7c0 <+12>:	sd	a4,40(sp)
   0x000000400080d7c2 <+14>:	sd	a5,48(sp)
   0x000000400080d7c4 <+16>:	sd	a6,56(sp)
   0x000000400080d7c6 <+18>:	sd	a7,64(sp)
   0x000000400080d7c8 <+20>:	fsd	fa0,80(sp)
   0x000000400080d7ca <+22>:	fsd	fa1,88(sp)
   0x000000400080d7cc <+24>:	fsd	fa2,96(sp)
   0x000000400080d7ce <+26>:	fsd	fa3,104(sp)
   0x000000400080d7d0 <+28>:	fsd	fa4,112(sp)
   0x000000400080d7d2 <+30>:	fsd	fa5,120(sp)
   0x000000400080d7d4 <+32>:	fsd	fa6,128(sp)
   0x000000400080d7d6 <+34>:	fsd	fa7,136(sp)
   0x000000400080d7d8 <+36>:	slli	a1,t1,0x1
   0x000000400080d7dc <+40>:	mv	a0,t0
   0x000000400080d7de <+42>:	add	a1,a1,t1
   0x000000400080d7e0 <+44>:	auipc	a2,0x16
   0x000000400080d7e4 <+48>:	ld	a2,-2040(a2) # 0x4000822fe8
   0x000000400080d7e8 <+52>:	jalr	a2
   0x000000400080d7ea <+54>:	mv	t1,a0
   0x000000400080d7ec <+56>:	ld	ra,72(sp)
   0x000000400080d7ee <+58>:	ld	a0,8(sp)
   0x000000400080d7f0 <+60>:	ld	a1,16(sp)
   0x000000400080d7f2 <+62>:	ld	a2,24(sp)
   0x000000400080d7f4 <+64>:	ld	a3,32(sp)
   0x000000400080d7f6 <+66>:	ld	a4,40(sp)
   0x000000400080d7f8 <+68>:	ld	a5,48(sp)
--Type <RET> for more, q to quit, c to continue without paging--
   0x000000400080d7fa <+70>:	ld	a6,56(sp)
   0x000000400080d7fc <+72>:	ld	a7,64(sp)
   0x000000400080d7fe <+74>:	fld	fa0,80(sp)
   0x000000400080d800 <+76>:	fld	fa1,88(sp)
   0x000000400080d802 <+78>:	fld	fa2,96(sp)
   0x000000400080d804 <+80>:	fld	fa3,104(sp)
   0x000000400080d806 <+82>:	fld	fa4,112(sp)
   0x000000400080d808 <+84>:	fld	fa5,120(sp)
   0x000000400080d80a <+86>:	fld	fa6,128(sp)
   0x000000400080d80c <+88>:	fld	fa7,136(sp)
   0x000000400080d80e <+90>:	addi	sp,sp,144
   0x000000400080d810 <+92>:	jr	t1
End of assembler dump.

基本调用流程

1.XX@plt

2.xx@got

3.plt表段起始地址

4._dl_runtime_resolve ()

到这里我们还需要知道

  1. _dl_runtime_resolve 是怎么知道要查找 printf 函数的
  2. _dl_runtime_resolve 找到 printf 函数地址之后,它怎么知道回填到哪个 GOT 表项

查看重定位信息

oslab@oslab-virtual-machine:/opt/riscv/bin/Linker/dynamic$ riscv64-unknown-linux-gnu-readelf -r test

Relocation section '.rela.plt' at offset 0x3f8 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000012000  000300000005 R_RISCV_JUMP_SLOT 0000000000010450 __libc_start_main@GLIBC_2.34 + 0
000000012008  000100000005 R_RISCV_JUMP_SLOT 0000000000010460 puts@GLIBC_2.27 + 0
3.快速链接

修改源码,二次调用printf函数

#include <stdio.h>
void print_banner()
{
    printf("Welcome to World of PLT and GOT\n");
}
int main(void)
{
    print_banner();
    print_banner();
    return 0;
}

其他步骤如上

比较两次的got表项的值

//一开始没有重定位的时候将 printf@got 填成 lookup_printf 的地址
void printf@plt()
{
address_good:
    jmp *printf@got   
lookup_printf:
    调用重定位函数查找 printf 地址,并写到 printf@got
	goto address_good;//再返回去执行address_good
}
gdb) disas
Dump of assembler code for function puts@plt:
   0x0000000000010460 <+0>:	auipc	t3,0x2
   0x0000000000010464 <+4>:	ld	t3,-1112(t3) # 0x12008 <puts@got.plt>
=> 0x0000000000010468 <+8>:	jalr	t1,t3
   0x000000000001046c <+12>:	nop
End of assembler dump.
(gdb) info r t3
t3             0x10430	66608
(gdb) c
Continuing.

Breakpoint 2, 0x0000000000010516 in print_banner () at test.c:4
4	    printf("Welcome to World of PLT and GOT\n");
(gdb) c
Continuing.

Breakpoint 3, 0x0000000000010468 in puts@plt ()
(gdb) disas
Dump of assembler code for function puts@plt:
   0x0000000000010460 <+0>:	auipc	t3,0x2
   0x0000000000010464 <+4>:	ld	t3,-1112(t3) # 0x12008 <puts@got.plt>
=> 0x0000000000010468 <+8>:	jalr	t1,t3
   0x000000000001046c <+12>:	nop
End of assembler dump.
(gdb) info r t3
t3             0x40008961a8	274886910376
(gdb) x 0x40008961a8
0x40008961a8 <puts>:	0xe44e7179
(gdb) disas 0x40008961a8
Dump of assembler code for function puts:
   0x00000040008961a8 <+0>:	addi	sp,sp,-48
   0x00000040008961aa <+2>:	sd	s3,8(sp)
   0x00000040008961ac <+4>:	auipc	s3,0xee
   0x00000040008961b0 <+8>:	ld	s3,-916(s3) # 0x4000983e18
   0x00000040008961b4 <+12>:	sd	s0,32(sp)
   0x00000040008961b6 <+14>:	sd	s1,24(sp)
   0x00000040008961b8 <+16>:	sd	s2,16(sp)
   0x00000040008961ba <+18>:	sd	ra,40(sp)
   0x00000040008961bc <+20>:	sd	s4,0(sp)
   0x00000040008961be <+22>:	mv	s2,a0
   0x00000040008961c0 <+24>:	jal	0x40008b47da <strlen>
   0x00000040008961c4 <+28>:	ld	s1,0(s3)
   0x00000040008961c8 <+32>:	mv	s0,a0
   0x00000040008961ca <+34>:	lw	a5,0(s1)
   0x00000040008961cc <+36>:	slli	a4,a5,0x30
   0x00000040008961d0 <+40>:	bgez	a4,0x400089626c <puts+196>
   0x00000040008961d4 <+44>:	mv	a0,s1
   0x00000040008961d6 <+46>:	lw	a5,192(a0)
   0x00000040008961da <+50>:	bnez	a5,0x4000896262 <puts+186>
   0x00000040008961dc <+52>:	li	a5,-1
   0x00000040008961de <+54>:	sw	a5,192(a0)
   0x00000040008961e2 <+58>:	ld	s4,216(a0)
   0x00000040008961e6 <+62>:	lui	a5,0x1
   0x00000040008961e8 <+64>:	auipc	a4,0xed
   0x00000040008961ec <+68>:	addi	a4,a4,-80 # 0x4000983198 <__io_vtables>
   0x00000040008961f0 <+72>:	sub	a4,s4,a4
   0x00000040008961f4 <+76>:	addi	a5,a5,-1745 # 0x92f
   0x00000040008961f8 <+80>:	bltu	a5,a4,0x40008962da <puts+306>
   0x00000040008961fc <+84>:	ld	a5,56(s4)
   0x0000004000896200 <+88>:	mv	a2,s0
   0x0000004000896202 <+90>:	mv	a1,s2
   0x0000004000896204 <+92>:	jalr	a5
   0x0000004000896206 <+94>:	bne	s0,a0,0x4000896268 <puts+192>

第一次

第二次快速链接

调试程序的步骤

没有配置好riscv64-unknown-linux-gnu-gdb和qemu相结合的调试环境

已完成

qemu-riscv64 -singlestep -g 1234 Hello
riscv64-unknown-elf-gdb Hello

#进入gdb调试模式后,需要执行target remote localhost:端口号 来连接qemu模拟器:
(gdb)target remote localhost:1234

#之后再gdb就可以正常调试RISCV程序了

链接器通常需要为每个符号调整两条 RV32I 指令。如

数据地址需要调整 lui 和 addi

代码地址需要调整 auipc 和 jalr。

链接器松弛(没太看懂)

Linker Relaxation in LLD - Chih-Mao Chen, Andes Technology - YouTube

Linker Relaxation - 知乎 (zhihu.com)

加载器

由操作系统实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2255740.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

长沙市的科技查新机构有哪些

中南大学图书馆科技查新站&#xff1a; 中南大学图书馆科技查新站成立于2003年12月&#xff0c;中南大学图书馆科技查新站作为教育部首批批准的科技查新工作站之一&#xff0c;具备了在全国范围内开展科技查新工作的专业资质。 长沙理工大学科技查新工作站&#xff1a; 长沙理…

Spring Data Elasticsearch

简介说明 spring-data-elasticsearch是比较好用的一个elasticsearch客户端&#xff0c;本文介绍如何使用它来操作ES。本文使用spring-boot-starter-data-elasticsearch&#xff0c;它内部会引入spring-data-elasticsearch。 Spring Data ElasticSearch有下边这几种方法操作El…

【Web】AlpacaHack Round 7 (Web) 题解

Treasure Hunt flag在md5值拼接flagtxt的文件里&#xff0c;如 d/4/1/d/8/c/d/9/8/f/0/0/b/2/0/4/e/9/8/0/0/9/9/8/e/c/f/8/4/2/7/e/f/l/a/g/t/x/t 访问已经存在的目录状态码是301 访问不存在的目录状态码是404 基于此差异可以写爆破脚本 这段waf可以用url编码绕过 做个lab …

【数字电路与逻辑设计】实验五 4人表决器

文章总览&#xff1a;YuanDaiMa2048博客文章总览 【数字电路与逻辑设计】实验五 4人表决器 一、实验内容二、设计过程&#xff08;一&#xff09;设置变量&#xff08;二&#xff09;真值表&#xff08;三&#xff09;表达式 三、源代码&#xff08;一&#xff09;代码说明&…

解决Tomcat运行时错误:“Address localhost:1099 is already in use”

目录 背景: 过程&#xff1a; 报错的原因&#xff1a; 解决的方法&#xff1a; 总结&#xff1a; 直接结束Java.exe进程&#xff1a; 使用neststat -aon | findstr 1099 命令&#xff1a; 选择建议&#xff1a; 背景: 准备运行Tomcat服务器调试项目时&#xff0c;程序下…

【C++】刷题强训(day13)--牛牛冲钻五、最长无重复字数组、重排字符串

目录 1、牛牛冲钻五 1. 题目 1.2 思路 1.3 代码实现 2、最长无重复子数组 2.1 题目 2.2 思路 2.3 程序实现 3、重排字符串 3.1 题目 3.2 思路 3.3 代码实现 刷题汇总&#xff1a;传送门&#xff01; 1、牛牛冲钻五 1. 题目 1.2 思路 由题可知&#xff0c;赢一局则…

Kafka单机及集群部署及基础命令

目录 一、 Kafka介绍1、kafka定义2、传统消息队列应用场景3、kafka特点和优势4、kafka角色介绍5、分区和副本的优势6、kafka 写入消息的流程 二、Kafka单机部署1、基础环境2、iptables -L -n配置3、下载并解压kafka部署包至/usr/local/目录4、修改server.properties5、修改/etc…

在做题中学习(78):数组中第K个最大元素

解法&#xff1a;快速选择算法 说明&#xff1a;堆排序也是经典解决topK问题的算法&#xff0c;但时间复杂度为&#xff1a;O(NlogN) 而将要介绍的快速选择算法的时间复杂度为: O(N) 先看我的前两篇文章&#xff0c;分别学习&#xff1a;数组分三块&#xff0c;随机选择基准…

学习记录,正则表达式, 隐式转换

正则表达式 \\&#xff1a;表示正则表达式 W: 表示一个非字&#xff08;不是一个字&#xff0c;例如&#xff1a;空格&#xff0c;逗号&#xff0c;句号&#xff09; W: 多个非字 基本组成部分 1.字符字面量&#xff1a; 普通字符&#xff1a;在正则表达式中&#xff0c;大…

加载内核映像文件

将kernel转换成elf文件格式&#xff0c;不能直接从loader直接跳转到0x100000&#xff0c;需要解析&#xff0c;提取出代码和数据出来&#xff0c;放到0x10000&#xff08;64kb&#xff09;的位置&#xff0c;1M的位置只是存放elf文件的位置。 4.10加载内核映像文件2 common/el…

11.27-12.5谷粒商城

目录 新增商品 1.上线会员服务 2. 获取分类关联的品牌 3.获取选定分类下的属性分组和属性 4.新增商品vo 5.保存商品信息 6.Spu检索 7.Sku商品检索 新增商品 1.上线会员服务 将会员服务注册到nacos注册中心&#xff0c;启用服务注册发现EnableDiscoveryClient。 同时新增…

【硬件接口】UART接口

本文章是笔者整理的备忘笔记。希望在帮助自己温习避免遗忘的同时&#xff0c;也能帮助其他需要参考的朋友。如有谬误&#xff0c;欢迎大家进行指正。 一、UART接口概要 UART接口&#xff0c;即通用异步接收器/发送器&#xff0c;是一种常用的串行通信协议&#xff0c;广泛应用…

python | print() 函数常被忽略的几点用法

在 python 编程中&#xff0c;print() 是最为基础和常用的函数。 也正因如此&#xff0c;print() 函数的一些基础用法常常被我们初学者所忽略&#xff0c;典型的有&#xff1a;换行问题、间隔符使用及格式化输出等。 一、print() 换行问题 1、默认情况下&#xff0c;每一个 …

VTK编程指南<五>:VTK中的坐标系统、空间变换及VTK矩阵详解

1、坐标系统 计算机图形学里常用的坐标系统主要有 4 种&#xff0c;分别是 Model 坐标系统、World 坐标系统、View坐标系统和 Display坐标系统(这些名词在不同的书里的中文表述均有所差别&#xff0c;所以直接使用英文名词表示)&#xff0c;此外还有两种表示坐标点的方式&#…

MaxEnt模型在物种分布模拟中如何应用?R语言+MaxEnt模型融合物种分布模拟、参数优化方法、结果分析制图与论文写作

目录 第一章 以问题导入的方式&#xff0c;深入掌握原理基础 第二章 常用数据检索与R语言自动化下载及可视化方法 第三章 R语言数据清洗与特征变量筛选 第四章 基于ArcGIS、R数据处理与进阶 第五章 基于Maxent的物种分布建模与预测 第六章 基于R语言的模型参数优化 第七…

【JavaEE 进阶(一)】SpringBoot(上)

博主主页: 33的博客 文章专栏分类:JavaEE ??我的代码仓库: 33的代码仓库?? ???关注我带你了解更多进阶知识 目录 1.前言2.Spring3.第一个SpringBoot程序4.Spring MVC 4.1建立连接 4.1.1RequestMapping使用 4.2请求 4.2.1传递单个参数4.2.2传递多个参数4.2.3传递一个对象…

银行项目网上支付接口调用测试实例

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 公司最近有一个网站商城项目要开始开发了&#xff0c;这几天老板和几个同事一起开着需求会议&#xff0c;讨论了接下来的业务规划和需求策略&#xff0c;等技术需求…

手机LCD分区刷新技术介绍

分区刷新也称为分区变频&#xff0c;LCD分区刷新功能的目的是将屏幕分为上下半区&#xff0c;分区显示不同帧率&#xff0c;上方区块High Frame Rate&#xff0c;下方区块Low Frame Rate。使用者可以动态自定义上方高刷显示区的结尾位置。 当前的智能手机屏幕上&#xff0c;显示…

TesseractOCR-GUI:基于WPF/C#构建TesseractOCR简单易用的用户界面

前言 前篇文章使用Tesseract进行图片文字识别介绍了如何安装TesseractOCR与TesseractOCR的命令行使用。但在日常使用过程中&#xff0c;命令行使用还是不太方便的&#xff0c;因此今天介绍一下如何使用WPF/C#构建TesseractOCR简单易用的用户界面。 普通用户使用 参照上一篇教…

flask创建templates目录存放html文件

首先&#xff0c;创建flask项目&#xff0c;在pycharm中File --> New Project&#xff0c;选择Flask项目。 然后&#xff0c;在某一目录下&#xff0c;新建名为templates的文件夹&#xff0c;这时会是一个普通的文件夹。 然后右击templates文件夹&#xff0c;选择Unmark as …