通常我们不会去关心指令重定位(relocation)的细节,编译器的ld过程已经帮助我们做好了。由于最近在移植CRIU,涉及到指令的重定位计算,不得不细细研究代码重定位的细节知识。之前的文章介绍了MIPS架构下函数跳转指令bal和jal和重定位过程,感兴趣的同学可以跳转到https://blog.csdn.net/weixin_38669561/article/details/100536803查看,本章通过分析lw 指令来分析从内存加载数据到寄存器的重定位的过程。

本文有点烧脑,看完注意休息 “_

一、准备工作和基础知识

可以跳过
首先看下面的示例汇编语句:

//test.S
ENTRY(__export_parasite_head_start).set noreorderlw a0, __valuejr ra__value:.long 0
END(__export_parasite_head_start)

这里lw a0,__value 就是我们要分析的汇编指令。我们用gcc编译

$ gcc -save-temps -g -c -fno-builtin -mno-abicalls test.S

其中 -save-temps 参数意味着保留中间临时文件,所以这条命令执行完成会发现当前目录多了两个文件,test.s和test.o。其中test.s就是GCC编译生成的中间编译文件,而test.o是汇编文件。编译文件和汇编文件有什么区别呢?首先要了解GCC编译是有4个过程,预编译(生成.i文件)-> 编译(生成.s文件)->汇编(生成.o文件)->链接(生成可执行文件,默认为a.out)。这里的test.s就是编译阶段产生的文件,test.o就是汇编阶段产生的文件,编译和汇编的区别可以通过查看文件内容得知:

//test.s.section .head.text, "ax"
.globl __export_parasite_head_start; .align 4; .type __export_parasite_head_start, @function; __export_parasite_head_start:.set noreorderlw $4, __export_parasite_cmdjr $31
__export_parasite_cmd:.long 2
.size __export_parasite_head_start, . - __export_parasite_head_start

可以看出test.s和test.S几乎没有差别,都是汇编指令。
test.o已经是ELF格式的文件了,所以不能直接打开,需要使用objdump命令

$ objdump -D test.o > test.o.dump

然后打开文件test.o.dump

test.o:     文件格式 elf64-tradlittlemips...
Disassembly of section .head.text:
0000000000000000 <__export_parasite_head_start>:0:    3c040000    lui a0,0x04:    3c010000    lui at,0x08:    64840000    daddiu  a0,a0,0c:   0004203c    dsll32  a0,a0,0x010:    0081202d    daddu   a0,a0,at14: 8c840000    lw  a0,0(a0)18: 03e00008    jr  ra000000000000001c <__export_parasite_cmd>:1c:    00000002    srl zero,zero,0x0...

汇编过程就是将汇编代码(test.s)转变成机器可以执行的指令(test.o),有些汇编语句和机器指令一一对应,不需要扩展,比如上面的 "jr ra" 指令。有些汇编指令可能扩展成多条机器指令,比如test.S里里面的 "lw a0, __value" 扩展成了6条指令

   0:    3c040000    lui a0,0x04:    3c010000    lui at,0x08:    64840000    daddiu  a0,a0,0c:   0004203c    dsll32  a0,a0,0x010:    0081202d    daddu   a0,a0,at14: 8c840000    lw  a0,0(a0)

同时我们有知道,此时的test.o还是没有做重定位的指令集,从起始地址"0000000000000000"就可以看出,或者使用file命令

$ file test.o
test.o: ELF 64-bit LSB relocatable, MIPS, MIPS64 rel2 version 1 (SYSV), not stripped

这里 "relocatable"就意味着这是需要重定位的文件,或者说需要做指令修正的文件。
那么重定位什么时候做呢?链接阶段。上面我在使用gcc工具时有一个参数 "-c" 。这个值意思是制作编译、汇编,不进行链接。链接过程主要包括了地址和空间分配、符号决议和重定位。

下面我们使用ld工具来进行test.o的链接过程。为了便于分析LW指令的重定位的过程,ld使用自定义的链接脚本,内容如下:

// ld.lds
OUTPUT_ARCH(mips)
ENTRY(__export_parasite_head_start) /*指定了程序入口函数*/
SECTIONS
{. = 0x120000000; /*0xfff70c4000;  指定当前虚拟地址*/tinytext : {  *(.head.text)*(.text*)*(.data)*(.rodata)}/DISCARD/ : { /*释义:需要丢弃的输入段*/*(.comment)*(.pdr)}/* Parasite args should have 4 bytes align, as we have futex inside. */
. = ALIGN(4);
__export_parasite_args = .;
}

在这个文件中,你只要关注两点, 一、"ENTRY(__export_parasite_head_start)"指定了程序运行的入口地址,没有它,接下来的ld命令会失败 。二、". = 0x120000000" 指定了当前虚拟起始地址。接下来执行l命令。

$ ld -static -T ld.lds -o test test.o

这时查看生成的test文件已经是可执行的,relocation已经完成。

$ file test
test: ELF 64-bit LSB executable, MIPS, MIPS64 rel2 version 1 (SYSV), statically linked, not stripped

二、relocation分析

敲黑板上面的内容都是准备工作,接下来开始分析重定位(relocation)的过程。
首先反汇编test文件

$ objdump -D test > test.dump

打开test.dump文件

test:     文件格式 elf64-tradlittlemips...Disassembly of section tinytext:000000fff70c4030 <__export_parasite_head_start>:fff70c4030:   3c040000        lui     a0,0x0fff70c4034:   3c01f70c        lui     at,0xf70cfff70c4038:   64840100        daddiu  a0,a0,256fff70c403c:   0004203c        dsll32  a0,a0,0x0fff70c4040:   0081202d        daddu   a0,a0,atfff70c4044:   8c84404c        lw      a0,16460(a0)fff70c4048:   03e00008        jr      ra000000fff70c404c <__export_parasite_cmd>:fff70c404c:   00000002        srl     zero,zero,0x0

发现和上面的test.o文件反汇编文件的不同点了吗?我把区别标记下来并开始分析:

其中蓝色部分是做了重定向的结果。 R_MIPS_HIGHEST、R_MIPS_HI16 、R_MIPS_HIGHER、R_MIPS_LO16是重定位入口类型。每种类型的指令修正方式可以通过查看mipsabi文档可以找到
目前我从https://elinux.org网站上下载下来的mipsabi.pdf文档里对重定向入口类型介绍的也不全,最好的分析办法是看 binutils 的源码
还要明确一下,我当前运行的是在龙芯处理器上。mips寄存器为64位,指令32位,寻址48位(我还不确定)。lw指令的描述是 lw rt,offset(base) ,这里base可以寻址64位。按上面的"lw a0,16460(a0)" 为例,base值应该是0xfff70c0000(等于ld.lds里面设置的初始虚拟地址) 。16460是10进制数,对应的16进制数为0x404c。那么寻址后的a0值应该为0xfff70c0000+0x404c = 0xfff70c404c。base值应该是(也就是上面的a0)

接下来我开始分析上面的每一条指令来了解lw a0,__export_parasite_cmd是怎么加载上来的。

第一条指令 lui a0,0x0

lui 功能为上位加载立即数,描述为a0 = 0x0<<16位,操作后的a0值为:
a0 :0x0000 0000 0000 0000

第二条指令 lui at,0xf70c

这里使用了at寄存器做中间变量,描述为at = 0xf70c<<16,操作后的at值为:
at:0xffff ffff f70c 0000
这里是最让人费解的地方,f70c左移16位后应该是0x0000 0000 f70c 0000。而这里却是0xffff ffff f70c 0000 这是我通过gdb调试确认过的结果,可能是MIPS实现48位内存寻址的策略

第三条指令 daddiu a0,a0,256

daddiu 功能为64位立即数加法,描述为a0 = a0+256 ,256为10进制对应0x0100,操作后的a0值为:
a0:0x0000 0000 0000 0100

第四条指令 dsll32 a0,a0,0x0

dsll32 功能为32位左移,描述为a0 = a0<<(32+0x0),操作后的a0值为:
a0:0x0000 0000 0100 0000

第五条指令 daddu a0,a0,at

daddu功能同daddiu,为64位加法,但是操作数不是立即数而是寄存器。描述为a0 = a0+at,结果为:
a0:0x0000 00ff f70c 0000
此时你可能明白点为啥at值的高32位填充全f的原因没?

分析的最后一条 lw a0,16460(a0)

lw 功能为32位加载,64位CPU上进行符号扩展。 描述为a0 = memory(0xff f70c 0000+0x404c),a0结果为:
a0 = 2 //如果不信,你可以使用gdb调试
也就是lw执行后,a0可以加载到内存地址为fff70c404c上的数据。

到这里,我们已经通过重定位的指令分析了lw的基址计算过程。如果让你去实现relocation过程,你该怎么做?可能我们不被逼到死路是不会考虑这个问题。此刻,我就在死路上。

敲黑板:指令修正方式

上面提到了重定位入口类型R_MIPS_HIGHEST、R_MIPS_HI16 、R_MIPS_HIGHER、R_MIPS_LO16,但是分析过程一点没有用到,那是由于ld已经帮我们根据这几个类型实现了指令修正。重定位入口类型只有在test.o 到可执行文件test 的汇编过程才会用到。test.o是ELF格式的文件,ELF格式中会存储哪些段需要重定位以及指令修正的类型等信息。我们可以通过readelf命令查看

$ readelf -r test.o重定位节 '.rela.head.text' 位于偏移量 0x4b0 含有 4 个条目:Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000000  00040000001d R_MIPS_HIGHEST    0000000000000000 .head.text + 1cType2: R_MIPS_NONE      Type3: R_MIPS_NONE
000000000004  000400000005 R_MIPS_HI16       0000000000000000 .head.text + 1cType2: R_MIPS_NONE      Type3: R_MIPS_NONE
000000000008  00040000001c R_MIPS_HIGHER     0000000000000000 .head.text + 1cType2: R_MIPS_NONE      Type3: R_MIPS_NONE
000000000014  000400000006 R_MIPS_LO16       0000000000000000 .head.text + 1cType2: R_MIPS_NONE      Type3: R_MIPS_NONE

offset是指当前指令在elf文件中的位置,Type即为重定位入口类型,Addend会参与到指令修正的运算。

MIPS上的重定位类型对应的计算方式可以通过mipsabs.pdf查看到一些(但不是全部),如下图:

在此我就上面的几个类型结合代码讲解test.o中relocation的计算过程。

重定位类型 R_MIPS_HIGHEST

通过elf格式解析过程能够看到test.o中第一条指令 lui a0,0x0 ,指令码为 3c040000,重定位类型为R_MIPS_HIGHEST。怎么计算呢?mipsabi上还真没有,我参考了binutils的源码中mips.cc得出计算过程为:
((vbase(0xfff70c4000)+ 0x800080008000llu)>>48) & 0xffff

上述的计算结果(0x0)放在lui指令的低16位。修正后的lui指令码还是 3c040000

重定位类型 R_MIPS_HI16

test.o中第二条指令 lui at,0x0,指令码为 3c010000,重定位类型 R_MIPS_HI16,计算方式根据不同的符号类型有不同的计算方式,此处的符号类型为Local,计算过程为:
((vbase(0xfff70c4000)+ 0x8000)>>16) & 0xffff
上述的计算结果(0xf70c)放在lui指令的低16位,修正后的lui指令码为 3c01f70c

重定位类型 R_MIPS_HIGHER

第三条指令 daddiu a0,a0,256 ,指令码为 64840000 ,重定位类型为R_MIPS_HIGHER,计算方式也只能参考binutils的源码中mips.cc文件
((vbase(0xfff70c4000)+ 0x80008000)>>32) & 0xffff
上述计算结果(0x0100)放在daddiu指令的低16位,修正后的daddiu指令码为64840100

重定位类型 R_MIPS_LO16 ( fixme)

第六条指令 lw a0,0(a0) ,指令码为 8c840000 ,重定位类型为R_MIPS_LO16,计算方式根据不同的符号类型有不同的计算方式,此处的符号类型为Local,计算过程为:
(vbase(0xfff70c4000)& 0xffff)+A (0x4c)

上述计算结果(0x404c)放在lw指令的低16位,修正后的lw指令码为8c84404c

MIPS架构下LW指令的重定位过程相关推荐

  1. MIPS架构下的逆向初探

    0x00 前言 本人是新接触mips下的逆向,本文参考了网上众多大佬的文章,权当是个人的学习总结,如有问题还请斧正. 本文主要是借助4道ctf里的题目来阐述. 参考文章: https://blog.c ...

  2. 【论文笔记】开放场景下的实时视觉重定位方法 HF-Net 2019

    HF-Net: From Coarse to Fine: Robust Hierarchical Localization at Large Scale 作者:Paul-Edouard Sarlin1 ...

  3. NFS - MIPS架构下构建NFS共享目录服务

    文章目录 概 Lin 和 Win 共享文件 需求 原理 环境信息 检查依赖 如何找mips的rpm包 NFS服务端 上传RPM安装包 安装RPCBIND和NFSSERVER 创建共享目录 配置文件/e ...

  4. [SystemVerilog] MIPS架构下的五级流水线CPU设计

    完整代码已上传 github 众所周知,MIPS体系的五级流水线CPU分为五个阶段:取指(IF).译码(ID).执行(EX).存储器(MEM).写回(WB).所以这根本算不上"设计" ...

  5. MIPS架构——汇编代码转机器代码编译器 Matlab GUI

    MIPS架构下的MCU,指令集包含R-Type.I-Type.J-Type三种,在数电课程设计时为了给MCU编写指令集,需要将汇编语言转化成机器代码,这里分享一下自己写的Matlab 的 GUI. 主 ...

  6. 24. PE结构-PE详解之基址重定位详解

    问题一:什么是基址重定位? 答:重定位就是你本来这个程序理论上要占据这个地址,但是由于某种原因,这个地址现在不能让你霸占,你必须转移到别的地址,这就需要基址重定位.打个比方:例如你现在计划在某某地方建 ...

  7. S5PV210裸机之重定位

    1.重定位相关概念 位置无关码(PIC,position independent code):汇编源文件被编译成二进制可执行文件时编码方式与位置(内存地址)无关.  位置有关码:汇编源文件被编译成二进 ...

  8. S5PV210体系结构与接口04:代码重定位 SDRAM初始化

    目录 1. C语言环境初始化 1.1 C语言运行所需环境 1.2 初始化栈 1.2.1 栈的概念 1.2.2 栈的作用 1.2.3 如何初始化 1.3 初始化bss段 1.3.1 bss段的作用 1. ...

  9. 嵌入式学习(二)——刷机和led实验(看门狗、c语言、icache、重定位、SDRAM)

    目录 一.刷机和裸机实验 1.1 刷机步骤 1.2 交叉编译链 1.2.1 环境变量配置 二.led实验 2.1 实验准备 2.2 实验开始 2.2.1 Makefile 2.2.2 mkv210_i ...

最新文章

  1. Hadoop基础-网络拓扑机架感知及其实现
  2. 假如 IDEA 也加入防沉迷功能...
  3. web前端技术分享:前端开发与后端开发的区别是什么?
  4. nginx1.9基于端口的四层负载均衡实践,基于端口的转的负载均衡
  5. Mysql索引是有序的吗_mysql组合索引的有序性转
  6. 小学有学计算机课程,如何进行小学计算机课程有效教学.doc
  7. http://blog.chinaunix.net/uid-20577907-id-3519578.html
  8. NHibernate从入门到精通系列(5)——持久对象的生命周期(下)
  9. jsx就是高级点的HTML拼接,JSX与HTML的那些不同
  10. 用批处理命令加WinRAR实现自动备份文件数据
  11. C#代码与javaScript函数的相互调用(转)
  12. 美国计算机科学教师协会,2020-2021 ACSL AMERICAN COMPUTER SCIENCE LEAGUE 美国计算机科学联赛...
  13. 5G无线关键技术 — 高频段信号传输技术
  14. 关于uIP移植以及部分特性解析和勘误
  15. 多线程爬取学习通题库
  16. 广州橙色优学:Java为什么这么火?Java好学吗?
  17. 简单说明CGI是什么
  18. 通达信经典实用选股公式
  19. 「Upwork高手攻略」月入1-4万,一个低门槛的大机会,入门指南
  20. Qt4.8.6移植到hi3559(四)

热门文章

  1. Git安装步骤嗷,详细到每一步
  2. 微量元素农业主导-国稻种芯-李喜贵:功能性农业两会档案
  3. CSDN修改用户名、昵称
  4. linux中写入log日志,Linux往log中写日志
  5. 牛客网--14664--锋线三叉戟
  6. 开源Hanlp自然语言处理Java实现(词法分析、关键词)
  7. 从费斯汀格法则看,永远不要对自己做的20件事
  8. 每天一个linux命令(44)--ss命令
  9. VMR9实现放大缩小
  10. 使用cmd打开Office Word、Excel、Powerpoint