我的编程空间,编程开发者的网络收藏夹
学习永远不晚

浅析ARMv8汇编指令adrp和adr

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

浅析ARMv8汇编指令adrp和adr

1.概述

在阅读Linux内核代码时,经常能碰到汇编代码,网上能查的资料千篇一律,大多都描述的很模糊。俗话说,实践是检验真理的唯一标准,我们就参考官方文档,自己写汇编代码并反汇编,探寻其中的奥妙。

2.adrp

在Linux内核启动代码primary_entry中,使用adrp指令获取Linux内核在内存中的起始页地址,页大小为4KB,由于内核启动的时候MMU还未打开,此时获取的Linux内核在内存中的起始页地址为物理地址。adrp通过当前PC地址的偏移地址计算目标地址,和实际的物理无关,因此属于位置无关码。对于具体的计算过程,下面慢慢分析。


[arch/arm64/kernel/head.S]
SYM_CODE_START(primary_entry)
    ......
	adrp	x23, __PHYS_OFFSET
	and	x23, x23, MIN_KIMG_ALIGN - 1  // KASLR offset, defaults to 0
    ......
SYM_CODE_END(primary_entry)

[arch/arm64/kernel/head.S]
#define __PHYS_OFFSET	KERNEL_START  // 内核的物理地址
[arch/arm64/include/asm/memory.h]
// 内核的起始地址和结束地址在vmlinux.lds链接脚本中定义
#define KERNEL_START    _text         // 内核代码段的起始地址,也即内核的起始地址
#define KERNEL_END		_end          // 内核的结束地址

2.1.定义

adrp指令根据PC的偏移地址计算目标页地址。首先adrp将一个21位有符号立即数左移12位,得到一个33位的有符号数(最高位为符号位),接着将PC地址的低12位清零,这样就得到了当前PC地址所在页的地址,然后将当前PC地址所在页的地址加上33位的有符号数,就得到了目标页地址,最后将目标页地址写入通用寄存器。此处页大小为4KB,只是为了得到更大的地址范围,和虚拟内存的页大小没有关系。通过adrp指令,可以获取当前PC地址±4GB范围内的地址。通常的使用场景是先通过adrp获取一个基地址,然后再通过基地址的偏移地址获取具体变量的地址。
下面是adrp指令的编码格式。立即数占用21位,在运行的时候,会将21位立即数扩展为33位有符号数。最高位为1,表示这是一个aarch64指令。

adrp

2.2.测试

Linux内核启动代码不好测试,需要写一个简单的测试代码。下面是本次adrp的测试代码,使用adrp指令获取g_val1g_val2数组所在页的基地址,同时会打印数组的地址和调用函数的地址,由于是应用层的程序,这些地址都是虚拟地址,但是计算过程都是一样的。


#define PAGE_4KB    (4096) 
#define __stringify_1(x...)	#x
#define __stringify(x...)	__stringify_1(x)
uint64_t g_val1[PAGE_4KB / sizeof(uint64_t)];
uint64_t g_val2[PAGE_4KB / sizeof(uint64_t)];

#define ADRP(label)   ({          \
    uint64_t __adrp_val__ = 0;    \
    asm volatile("adrp %0," __stringify(label) :"=r"(__adrp_val__)); \
    __adrp_val__;                 \
})

static void adrp_test()
{
    printf("g_val1 addr 0x%lx, adrp_val1 0x%lx, adrp_test addr 0x%lx\n",
        (uint64_t)g_val1, ADRP(g_val1), (uint64_t)adrp_test);
    printf("g_val2 addr 0x%lx, adrp_val2 0x%lx, adrp_test addr 0x%lx\n",
        (uint64_t)g_val2, ADRP(g_val2), (uint64_t)adrp_test);
}

上面程序运行的输出结果如下,g_val1g_val2的地址分别为0x5583e250280x5583e26028g_val1的页基地址为0x5583e25000g_val2页的基地址为0x5583e26000adrp_test函数的地址为0x5583e1479c


g_val1 addr 0x5583e25028, adrp_val1 0x5583e25000, adrp_test addr 0x5583e1479c
g_val2 addr 0x5583e26028, adrp_val2 0x5583e26000, adrp_test addr 0x5583e1479c

反汇编代码如下所示。下面分析一下g_val1页基地址的计算过程,包括编译时和运行时,g_val2页基地址的计算过程类似,这里不再赘述。

  • g_val1址低低12位清零,得到0x1100,将当前adrp指令所在地址的低12清零,得到0x0(编译时完成)
  • 0x1100减去0x0得到偏移地址0x11000,偏移地址右移12位得到偏移页数量0x11,将立即数0x11保存到指令编码中(编译时完成)
  • 取出立即数0x11,左移12位转换成偏移的字节数,即0x11000(运行时完成)
  • 将PC地址的低12位清零得到0x5583e14000(运行时完成)
  • 将0x5583e14000加上0x1100得到g_val1运行时页基地址0x5583e25000(运行时完成)

000000000000079c <adrp_test>:  // 运行时的地址为0x5583e1479c
......
 7b0:	b0000080 	adrp	x0, 11000 <__data_start>    // 获取g_val1页基地址
......
 7e0:	d0000080 	adrp	x0, 12000 <g_val1+0xfd8>    // 获取g_val2页基地址

Disassembly of section .data:       // 数据段定义
0000000000011000 <__data_start>:    // 运行时的地址为0x5583e25000
	...
......
Disassembly of section .bss:        // bss段定义
0000000000011028 <g_val1>:    // 运行时地址为0x5583e25028
	...
0000000000012028 <g_val2>:    // 运行时地址为0x5583e26028
	...

从上面可以看出,编译时和运行时的地址不一样,但通过adrp指令都能正确获取g_val1页基地址和g_val2页基地址。说明adrp获取的地址是位置无关的,不管运行时的地址怎么变,都可以正确获取对应变量页基地址。当然我们也可以使用专业的反汇编工具,直接将机器码转换为汇编代码。上面两条adrp指令转换的汇编代码如下,和上面一样,这里的偏移地址都已经做了左移12位的处理。

rasm2-adrp

3.adr

3.1.定义

adr指令根据PC的偏移地址计算目标地址。偏移地址是一个21位的有符号数,加上当前的PC地址得到目标地址。adr可以获取当前PC地址±1MB范围内的地址。下面是adr指令的编码格式。立即数占用21位。

adr

3.2.测试

下面是测试代码,使用adr指令获取变量g_val3g_val4的地址,并与通过&获取的地址进行对比。


uint64_t g_val3 = 0;
uint64_t g_val4 = 0;

#define ADR(label)   ({          \
    uint64_t __adr_val__ = 0;    \
    asm volatile("adr %0," __stringify(label) :"=r"(__adr_val__)); \
    __adr_val__;                 \
})

static void adr_test()
{
    printf("g_val3 addr 0x%lx, adr_val1 0x%lx, adr_test addr 0x%lx\n",
        (uint64_t)&g_val3, ADR(g_val3), (uint64_t)adr_test);
    printf("g_val4 addr 0x%lx, adr_val2 0x%lx, adr_test addr 0x%lx\n",
        (uint64_t)&g_val4, ADR(g_val4), (uint64_t)adr_test);
}

下面是测试结果,使用&获取的地址和通过adr获取的地址相同。


g_val3 addr 0x5583e25018, adr_val1 0x5583e25018, adr_test addr 0x5583e14810
g_val4 addr 0x5583e25020, adr_val2 0x5583e25020, adr_test addr 0x5583e14810

下面是反汇编的代码。可以看出,adr汇编代码中的偏移地址被objdump使用符号地址代替了,没有使用真正的偏移地址。g_val3真正的偏移地址为0x107f4,g_val4真正的偏移地址为0x107cc。执行第一条adr指令的PC地址为0x5583e14824,则0x5583e14824+0x107f4=0x5583e25018为g_val3的地址。g_val4的计算过程类似,不再赘述。


0000000000000810 <adr_test>:    // 运行地址为0x5583e14810
......
 824:	10083fa0 	adr	x0, 11018 <g_val3>  // 偏移地址为0x11018-0x824=0x107f4
......
 854:	10083e60 	adr	x0, 11020 <g_val4>  // 偏移地址为0x11020-0x854=0x107cc
......

isassembly of section .data:

0000000000011000 <__data_start>:
	...
......
Disassembly of section .bss:
......
0000000000011018 <g_val3>:      // 运行地址为0x5583e25018
	...

0000000000011020 <g_val4>:      // 运行地址为0x5583e25020
    ...

rasm2-adr

参考资料

  1. linux-5.10.81原代码
  2. Arm ® Architecture Reference Manual Armv8, for A-profile architecture

到此这篇关于ARMv8汇编指令-adrp和adr的文章就介绍到这了,更多相关ARMv8汇编指令内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

浅析ARMv8汇编指令adrp和adr

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

ARMv8汇编指令adrp和adr怎么使用

这篇文章主要介绍“ARMv8汇编指令adrp和adr怎么使用”,在日常操作中,相信很多人在ARMv8汇编指令adrp和adr怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”ARMv8汇编指令adrp和a
2023-06-22

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录