了解用于函数调用的 auipc+jalr 序列

2023-11-27

我试图阅读RISC-V生成的程序集gcc我发现gcc创建序列auipc+jalr对于某些函数调用,我不明白它是如何工作的。这是一个简单的例子。考虑以下C源文件:

unsigned long id(unsigned long x) {
    return x;
}

unsigned long add_one(unsigned long x) {
    return id(x)+1;
}

我用它编译gcc -O2 -fno-inline -c test.c我得到以下汇编代码:

$ objdump -d test.o

test.o:     file format elf64-littleriscv


Disassembly of section .text:

0000000000000000 <id>:
   0:   00008067            ret

0000000000000004 <add_one>:
   4:   ff010113            addi    sp,sp,-16
   8:   00113423            sd      ra,8(sp)
   c:   00000317            auipc   t1,0x0
  10:   000300e7            jalr    t1
  14:   00813083            ld      ra,8(sp)
  18:   00150513            addi    a0,a0,1
  1c:   01010113            addi    sp,sp,16
  20:   00008067            ret

让我困惑的是偏移处的两条线0x0c and 0x10,这就是函数的位置id应该被称为。根据spec, auipc t1,0x0应该写PC + 0x0<<12(这等于PC) to t1进而jalr t1(它被扩展到jalr ra,t1,0) 跳转到存储的地址t1并将返回地址存储到ra。所以我们最终跳到auipc线(偏移0x0c),不是入口点id。这里发生了什么?


反汇编目标文件时,显示的地址信息auipc/jalr有点任意,因为无论如何它都会被链接器重新定位。

您可以看到,在转储重定位信息时(添加-r到您的 objdump 调用):

0000000000000000 <id>:
   0:   8082                    ret
0000000000000002 <add_one>:
   2:   1141                    addi    sp,sp,-16
   4:   e406                    sd  ra,8(sp)
   6:   00000097            auipc   ra,0x0
            6: R_RISCV_CALL id
            6: R_RISCV_RELAX    *ABS*
   a:   000080e7            jalr    ra # 6 <add_one+0x4>
   e:   60a2                    ld  ra,8(sp)
  10:   0505                    addi    a0,a0,1
  12:   0141                    addi    sp,sp,16
  14:   8082                    ret

这些重定位条目告诉链接器以轻松的方式重定位跳转指令(RISC-V 工具链的默认设置)。这意味着它可以替换auipc+jalr仅与一个配对jal指令 iff 到目标地址的距离足够短。这种替换是有利的,因为它节省了指令,即生成的程序更短。显然,它使重定位过程变得有点复杂,因为后续跳转指令的偏移量需要相应调整。

(这可以通过禁用-mno-relax海湾合作委员会旗帜。)

为什么汇编器不能直接发出finalauipc/jalr/jal不需要重新定位翻译单元本地符号的指令?毕竟,这些跳跃是与电脑相关的。

一般来说,它不能,因为只有一个翻译单元的本地视图 1) 到外部符号的宽松重定位可能会更改所有后续到内部符号的偏移量,2) 链接器甚至可能应用一些高级规则,例如其中内部符号被外部符号覆盖,因此它确实必须在链接器中重新定位。或者,另一个例子,链接器删除了一个符号。

如果您想查看重定位的地址/偏移量,您必须反汇编链接的二进制文件,例如:

000000000001015c <id>:
   1015c:   8082                    ret
000000000001015e <add_one>:
   1015e:   1141                    addi    sp,sp,-16
   10160:   e406                    sd  ra,8(sp)
   10162:   ffbff0ef            jal ra,1015c <id>
   10166:   60a2                    ld  ra,8(sp)
   10168:   0505                    addi    a0,a0,1
   1016a:   0141                    addi    sp,sp,16
   1016c:   8082                    ret

As expected, the linker relaxes auipc+jalr to just jal. Unfortunately, objdump doesn't display the raw jal offset - 1015c is the absolute address after adding the offset to 10162.1

您可以自行解码第二列的二进制指令来验证:

   0xffbff0ef
=  0b11111111101111111111000011101111 | split into the offset parts
=>   1 1111111101 1 11111111          | i.e. off[20], off[10:1], off[11], off[19:12]
                                      | merge them into off[20:1]
=> 0b11111111111111111101             | left-shift by 1
=> 0b111111111111111111010            | sign-extend
=> 0b11111111111111111111111111111010
=  -6
=> 0x10162 - 6
=  0x1015c

这与 objdump 输出匹配。


1 That means GNU binutils objdump doesn't display the raw jal offset. In contrast, llvm-objdump (LLVM 9 introduces official RISC-V support) does display the raw offset:

000000000001015e add_one:
   1015e: 41 11                         addi    sp, sp, -16
   10160: 06 e4                         sd  ra, 8(sp)
   10162: ef f0 bf ff                   jal -6
   10166: a2 60                         ld  ra, 8(sp)
   10168: 05 05                         addi    a0, a0, 1
   1016a: 41 01                         addi    sp, sp, 16
   1016c: 82 80                         ret

然而,与 GNU binutils objdump 相比,llvm-objdump不包括结果绝对地址作为注释。它也没有注释相应的符号。因此,一般来说,GNU binutils objdump 输出可以说更有用。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

了解用于函数调用的 auipc+jalr 序列 的相关文章

  • 为什么 VC++ 编译器 MOV+PUSH args 而不是仅仅 PUSH 它们? x86

    在 VC 的反汇编中 正在进行函数调用 编译器在压入本地指针之前将其 MOV 到寄存器 memcpy nodeNewLocation pNode sizeCurrentNode 0041A5DA 8B 45 F8 mov eax dword
  • nasm/ld“重定位被截断以适合:R_386_16”

    集会 BITS 16 global start start mov ax 0x07C0 mov ds ax mov si hw call print string jmp print string mov ah 0x0E char lods
  • x86 程序执行期间方向标志 (DF) 的默认状态

    在反汇编中 我经常看到使用字符串操作指令而不考虑方向标志 DF 的状态 如下所示 or ecx 0FFFFFFFFh xor eax eax mov edi ebp repne scasb CLD or STD自函数开始以来未找到指令 也未
  • 电路解码所需的最小输入位数

    我正在学习计算机体系结构 并且正在阅读有关编码器和解码器的内容 在 MIPS 处理器中 操作码有 6 位 我想知道构建解码器来解码操作码需要多少输入位 我知道解码器是一个组合电路 它将二进制信息从 n 个输入线转换为最多 2 n 个唯一的输
  • GCC 内联 asm NOP 循环在编译时未展开

    走出我通常的 VC 领域 进入 GCC 的世界 通过 MINGW32 尝试创建一个主要由 NOP 组成的 Windows PE ala for i 0 i lt 1000 i asm nop 但要么我使用了错误的语法 要么编译器正在通过它们
  • LDR指令如何将常量加载到寄存器中?

    我刚刚读了一本ARM指令书 看到一条指令我无法解释 It says LDR将 32 位常量加载到r0登记 LDR r0 pc const number 8 pc const number DCD 0xff00ffff 我不明白什么 pc c
  • C 结构如何返回[重复]

    这个问题在这里已经有答案了 我想知道如何返回一个结构 例如 typedef struct number uint64 t a b c d number number get number number res 0 0 0 0 return
  • 本机代码、机器代码和汇编代码有什么区别?

    我对 NET 语言上下文中的机器代码和本机代码感到困惑 它们之间有什么区别 它们是一样的吗 这些术语确实有点令人困惑 因为它们有时使用不一致 机器代码 这是定义最明确的一种 它是使用字节码指令的代码 您的处理器 执行实际工作的物理金属部件
  • 为什么每次在 GDB 中构建和反汇编函数时都会得到相同的地址?

    每次反汇编函数时 为什么总是得到相同的指令地址和常量地址 例如 执行以下命令后 gcc o hello hello c ggdb gdb hello gdb disassemble main 转储代码将是 当我退出 gdb 并重新反汇编 m
  • MS-DOS - 是否可以对 24 位图形进行编程?

    是否可以在 DOS 机器上以 24 位颜色深度进行编程 我知道 VGA 支持 8 位色深 但是有没有办法弄出 24 位色深 谷歌的研究没有发现任何结果 我正在 FreeDOS 而不是 MS DOS 上编程 如果这会影响答案的话 对的 这是可
  • 段错误...关于你好世界

    这段代码非常简单 但我在 x86 64 Linux 系统上遇到了段错误 这让我很烦恼 刚开始接触asm 请耐心等待 与 NASM 组装nasm f elf64 test asm 与连接ld o test test o SECTION tex
  • 为什么 x86-64 上的 GCC 在函数内插入 NOP?

    给定以下 C 函数 void go char data char name 64 strcpy name data x86 64 上的 GCC 5 和 6 编译 普通gcc c g o其次是objdump 这到 00000000000000
  • _addcarry_u64 和 _addcarryx_u64 与 MSVC 和 ICC

    MSVC 和 ICC 都支持内在函数 addcarry u64 and addcarryx u64 根据英特尔的内在指南 https software intel com sites landingpage IntrinsicsGuide
  • 在汇编中使用 printf 会导致管道传输时输出为空,但可以在终端上使用

    无输出 https stackoverflow com questions 54507957 printf call from assembly do not print to stdout即使在终端上 当输出不包含换行符时也有相同的原因
  • 汇编程序中的过程调用如何工作?

    我刚刚开始摆弄 ASM 我不确定我对过程调用的理解是否正确 假设代码中的某个时刻有一个过程调用 call dword ptr 123 该过程仅包含一个命令 ret ret 0004 该过程调用的效果是什么 返回值将存储在哪里 我在某处读到
  • 将 AT&T 语法转换为 INTEL 语法

    我发现这个 GAS 文件包含一些可以从 CD 启动的引导加载程序代码 我想研究它并尝试制作我自己的一个 但唯一的问题是它采用 AT T 语法而不是 Intel 语法 我对 AT T 语法一无所知 我尝试过使用 Intel2gas 转换器 但
  • 函数地址不是实际代码地址

    在 Visual Studio 2008 C 中调试一些代码时 我注意到函数指针变量中的地址不是函数本身的实际地址 这是一个外部 C 函数 int main void printaddr const char print debug sho
  • 如何在 x86 ASM 中将整数转换为浮点值?

    我需要将一个整数 二进制补码 乘以一个浮点常数 这是我所拥有的 data pi dd 3 14 int dd 0ah code fld pi fmul ST 1 ST 我怎样才能转换int乘以浮点值pi 你需要fild操作说明 这是一个参考
  • 使用8086汇编语言画圆[关闭]

    Closed 这个问题是无法重现或由拼写错误引起 help closed questions 目前不接受答案 我试图使用 8086 汇编器画一个圆 我尝试利用中点圆算法 https en wikipedia org wiki Midpoin
  • 3 操作数 imul 指令在 ia-32 汇编中到底起什么作用?

    我正在阅读说明 imul 0xffffffd4 ebp ebx 4 eax 我对它到底在做什么感到困惑 我明白那个imul乘法 但我无法弄清楚语法 我知道并且更喜欢 Intel MASM 语法 所以我将使用它 请注意 操作数的顺序在 AT

随机推荐

  • 如何使用 Spark DataFrames 查询 JSON 数据列?

    我有一个 Cassandra 表 为简单起见 它看起来像 key text jsonData text blobData blob 我可以使用 Spark 和 Spark cassandra connector 为此创建一个基本数据框架 v
  • 从响应标头中删除“服务器:Apache”

    我想知道如何完全删除 apache 在响应中发送的服务器标头 最初 它显示完整的服务器信息 例如Server Apache Ubuntu 14 04 在响应标头中 但我在某处读到将其添加到 apache2 conf 中 ServerToke
  • 如何将手机与邮箱/密码验证关联?

    我正在尝试创建一个 flutter 应用程序 在其中使用电子邮件 密码创建用户后 将其保存在 firebase 中 然后用户输入发送 OTP 的电话号码 并在验证后登录 我的问题是 当这两个步骤完成后 Firebase 将创建两个单独的帐户
  • 使用 Serde 反序列化对象时,有没有办法省略包装器/根对象?

    我有以下对象 data id 1 name South America countries data id 122 name Brazil capital Brasilia 我想定义两个结构 Continent and Country 省略
  • 为什么 GetHashCode 位于 Object 类中?

    Why 获取哈希码是对象类的一部分吗 只有一小部分类的对象用作哈希表中的键 当我们希望类的对象作为哈希表中的键时 拥有一个必须实现的单独接口不是更好吗 MS 团队决定将此方法包含在对象类中 从而使其 随处 可用 肯定是有原因的 在我看来 这
  • 如何将当前屏幕捕获为图像?

    我想添加用户在我的应用程序中捕获当前屏幕并通过电子邮件发送的功能 我有一个非常非技术性的用户群 所以我需要它尽可能简单 我打算让他们单击名为 帮助我 的菜单项 然后 它将捕获当前的应用程序屏幕 最好是 jpg 或 png 格式 然后打开 O
  • Git 接收后挂钩不起作用

    我的设置是 Windows XAMPP 服务器 启用了 cURL 并安装了 Git 和 Hudson Hudson 每分钟轮询一次 Git 以查找更改 如果找到更改 就会创建一个构建 我使用这个版本作为我的测试服务器 这很好用 我想在我的中
  • Chart.js 压缩条形图上的垂直轴

    我有一个数据集 其中最后一个值总是非常高 这会导致我的条形图出现问题 几乎所有其他值如果不悬停在它们上面就很难有感觉 这是一个屏幕截图 这就是我想要达到的目标 所以我的问题是 这可以在普通 Chart js 中实现吗 或者我需要一个插件吗
  • 在 JavaScript (node.js) 中设置错误原因

    是否可以指定 JavaScript node js 中错误的原因 我找到了Mozilla 文档它定义了如何设置消息 文件 行 但不定义错误原因 我对此感兴趣的原因是我想捕获内部错误并将其传播到嵌套异常中的表面 类似于 Java 中的异常链
  • 如何从类似文件的对象中使用 python mimetypes.guess_type

    gt gt gt mimetypes guess type picture jpg image jpeg None 现在我有一个类似文件的对象 例如 stingIO 哪些内容是图像的数据 我如何从类似文件的对象中检测 mimetypes p
  • 显示内存中的 RGBA 图像

    我有一个包含 RGBA 编码图像的 C 字节数组 在 WPF 中显示此图像的最佳方式是什么 一种选择是从字节数组创建 BitmapSource 并将其附加到 Image 控件 但是 创建 BitmapSource 需要 RGBA32 的 P
  • Django 模板无法循环defaultdict

    import collections data firstname John lastname Smith firstname Samantha lastname Smith firstname shawn lastname Spencer
  • 如何捕获 Windows Azure(辅助)角色中未处理的异常

    我试图捕获我的工作角色中所有未处理的异常 我尝试放一个try catch块进入Run 方法 按照建议here 但没有成功 public override void Run try base Run catch Exception ex Tr
  • 检查 Python 中是否存在某种*类型*的文件

    我意识到这看起来与检查文件是否存在的其他问题类似 但它是不同的 我正在尝试找出如何检查type文件存在 如果不存在则退出 我最初尝试的代码是这样的 filenames os listdir os curdir for filename in
  • 如何使用composer安装部分git存储库?

    我只想需要 git 存储库的子部分 而不是完整的东西 我想这样做的原因是因为存储库很大 就我而言 存储库是 https github com pubnub pubnub api git我只想要 php 目录 我在作曲家中定义了以下包 typ
  • OpenCV功能类似于matlab的“查找”

    我正在寻找 openCV 中的一个函数来帮助我制作图像蒙版 例如在 MATLAB 中 B A or B 零 大小 A B A 10 c 有些功能可以让你通过mask向他们提出论据 按照您描述的方式创建面具 我认为您正在追求Cmp 或 Cmp
  • 检查 Intent.ACTION_VIEW 上的用户操作

    我有一个填充了一些文件的列表视图 可以有多种类型 如 pdf 或文档 当用户单击一个文件时 我会获取文件 mime 类型并启动一个意图 让用户选择使用哪个应用程序来打开该文件 什么我想知道用户是否选择了某些内容 或者只是按回键而没有选择任何
  • 在 python 中填充队列并管理多处理

    我在 python 中遇到这个问题 我有一个 URL 队列 需要时不时地检查一下 如果队列已满 我需要处理队列中的每个项目 队列中的每个项目必须由单个进程处理 多处理 到目前为止 我设法 手动 实现这一点 如下所示 while 1 self
  • 带有组合 API 的 Vue 3 事件总线

    我已经设置了mitt并尝试将事件分派到另一个组件 但我遇到了困难 因为在setup 它没有的方法this用于访问应用程序实例 这是我尝试过的 import App from App vue const el document getElem
  • 了解用于函数调用的 auipc+jalr 序列

    我试图阅读RISC V生成的程序集gcc我发现gcc创建序列auipc jalr对于某些函数调用 我不明白它是如何工作的 这是一个简单的例子 考虑以下C源文件 unsigned long id unsigned long x return