千家信息网

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

发表于:2024-11-18 作者:千家信息网编辑
千家信息网最后更新 2024年11月18日,这篇文章主要介绍"ARMv8汇编指令adrp和adr怎么使用",在日常操作中,相信很多人在ARMv8汇编指令adrp和adr怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对
千家信息网最后更新 2024年11月18日ARMv8汇编指令adrp和adr怎么使用

这篇文章主要介绍"ARMv8汇编指令adrp和adr怎么使用",在日常操作中,相信很多人在ARMv8汇编指令adrp和adr怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"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,表示这是一个aarch74指令。

    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 0x5583e1479cg_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 :  // 运行时的地址为0x5583e1479c...... 7b0:      b0000080   adrp    x0, 11000 <__data_start>    // 获取g_val1页基地址...... 7e0:      d0000080   adrp    x0, 12000     // 获取g_val2页基地址Disassembly of section .data:       // 数据段定义0000000000011000 <__data_start>:    // 运行时的地址为0x5583e25000        .........Disassembly of section .bss:        // bss段定义0000000000011028 :    // 运行时地址为0x5583e25028        ...0000000000012028 :    // 运行时地址为0x5583e26028        ...

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

    3.adr

    3.1.定义

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

    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 0x5583e14810g_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 :    // 运行地址为0x5583e14810...... 824:      10083fa0   adr     x0, 11018   // 偏移地址为0x11018-0x824=0x107f4...... 854:      10083e60   adr     x0, 11020   // 偏移地址为0x11020-0x854=0x107cc......isassembly of section .data:0000000000011000 <__data_start>:        .........Disassembly of section .bss:......0000000000011018 :      // 运行地址为0x5583e25018        ...0000000000011020 :      // 运行地址为0x5583e25020    ...

    到此,关于"ARMv8汇编指令adrp和adr怎么使用"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

    地址 指令 偏移 运行 代码 基地 基地址 内核 测试 目标 过程 起始 号数 所在 学习 编译 内存 变量 大小 物理 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 录像机和服务器有什么不同 苹果服务器系统查看 网络安全等级保护工作检查规范 大学校园网络安全教育课堂 南京电大试卷数据库 关于冬奥会期间网络安全保障情况工作报告 软件开发商业计划书附录 武汉软件开发单休还是双休 中国最好的服务器厂 奉贤区推广软件开发厂家职责 服务器jar包管理 主题网络安全的公众号 2021年北京市网络安全大会 window服务器如何租用 恶灵进化2自创服务器 计算机网络技术课程学什么 网络技术测评服务费 日照鹏昇网络技术有限公司 云服务器可以存放链接 海康安防管理平台中心服务器 南京邮电大学网络技术学院 怎样设置域名服务器 软件开发用图 连接官方服务器失败可以连的上吗 通信服务器软件 幻塔服务器5倍经验 吃鸡哪个服务器不卡 cobol中怎样使用数据库 数据库原理与应用 刘征海 无代码的数据库管理平台
    0