本 LoongArch 指令集研究工作在百度贴吧龙芯吧同步连载。
本研究中涉及的逆向工程仅出于学习、研究目的。本研究工作未得到任何龙芯、麒麟等软硬件厂商的任何形式帮助。
本研究属于个人行为,与本人雇主或任何其他主体无关。
前面我们搞了个土办法,已经能借助 MIPS 的工具链方便地检查单个函数的代码了。现在找两个小函数开刀:
const char *
_Py_gitversion(void)
{
return GITVERSION;
}
0000000000052b94 <_Py_gitversion>:
52b94: 02ffc063 0x2ffc063
52b98: 29c02076 slti zero,t2,8310
52b9c: 02c04076 0x2c04076
52ba0: 1c004b6c bgtz zero,65954 <_PyObject_CallMethod_SizeT+0x60>
52ba4: 02fb418c syscall 0xbed06
52ba8: 00150184 0x150184
52bac: 28c02076 slti zero,a2,8310
52bb0: 02c04063 0x2c04063
52bb4: 4c000020 0x4c000020
以及这个也比较小:
void
PyNode_ListTree(node *n)
{
listnode(stdout, n);
}
0000000000053368 <PyNode_ListTree>:
53368: 02ff8063 0x2ff8063
5336c: 29c06061 slti zero,t2,24673
53370: 29c04076 slti zero,t2,16502
53374: 02c08076 0x2c08076
53378: 29ffa2c4 slti ra,t3,-23868
5337c: 1c0076ac bgtz zero,70e30 <stringlib_isspace+0xd8>
53380: 28dab18c slti k0,a2,-20084
53384: 2600018c addiu zero,s0,396
53388: 28ffa2c5 slti ra,a3,-23867
5338c: 00150184 0x150184
53390: 54001800 0x54001800
53394: 03400000 0x3400000
53398: 28c06061 slti zero,a2,24673
5339c: 28c04076 slti zero,a2,16502
533a0: 02c08063 0x2c08063
533a4: 4c000020 0x4c000020
这两个函数第一眼看上去,有什么相似之处?
其实多看几个函数就会发现,好像所有函数都是以 02ffxx63
开头,以 02c0xx63 4c000020
结尾。
还有,紧贴着开头和结尾,往往是相同数量的 29c0xxxx
和 28c0xxxx
。
根据一般的人类认知,很显然,4c000020
应该是从子程序调用返回的意思。而 02
开头的指令,更像是起到了调整栈指针——sp
的作用。
我们把这三个指令用 LoongArch 的指令格式试着分析一下!
02ff8063 = 0000 0010 1111 1111 1000 0000 0110 0011
= 000000 1011111111100000 00011 00011
02c08063 = 0000 0010 1100 0000 1000 0000 0110 0011
= 000000 1011000000100000 00011 00011
4c000020 = 0100 1100 0000 0000 0000 0000 0010 0000
= 010011 0000000000000000 00001 00000
我们先来看前两条指令。看上去像是同一个操作,而且大量连续的 1 和 0,怎么看怎么像两个互为相反数的立即数!
日常生活中常见架构的栈,无一例外都是往下增长的,要支持相反的增长方向,从工具链到内核到各种底层工具,需要填平相当大量的坑。因此我们有足够的理由相信 LoongArch 也不会例外。
鉴于我们处理的会是栈指针 sp
的增减这样一件事,那么显然变化量不会很大。事实上,几百字节的栈帧对绝大多数函数来讲,已经算“巨大”了。这样的话,我们应该能精确“抠”出前两条指令的立即数域了。
000000 1011 111111100000 00011 00011
OPC OPC2 IMM RJ RD
000000 1011 000000100000 00011 00011
OPC OPC2 IMM RJ RD
中间一段的断句一定在 1011
处,不能更靠后,因为两个指令的相同前缀止于此;不能更靠前,因为否则下面这个数的最高位就会为 1,两个数就都变成负数了。
那么这就是 LoongArch 的标准指令格式之一——双寄存器操作数 12 位立即数。两个寄存器都是 r3
,两个立即数确实是相反数(正负 32,大家可以复习一下 2 的补码表示),第一个是负数。
于是我们现在有足够的信息,假定这个指令就是 LoongArch 的立即数有符号 64 位加法了。为啥一定是 64 位呢?因为 loongarch64
剧透了啊 ;-)
000000 1011 IMM12 RJ RD => addi rd, rj, imm12
这里借用了 RISC-V 的指令名(所以以后发现的 32 位立即数加法会叫 addiw
)。另外,r3
寄存器应该就是 LoongArch 约定的栈指针了。
为了一篇文章不要太长,剩下的指令,就留到下回分解吧~