全网Linux内核中att风格的汇编指令方法

云平台

  汇编的语法风格分为两种,一种是intel风格,一种是at&t风格,intel风格主要用于windows平台,at&t风格主要用于unix平台。

  因为linux是类unix型的操作系统,所以其内核中的汇编代码也是使用的at&t风格。

  在编译linux内核时,默认使用的编译器是gcc,当涉及到内核汇编代码的编译时,gcc通过调用gnu的as命令来完成,as命令官方文档地址如下:

  https://sourceware.org/binutils/docs-2.34/as/index.html

  既然linux内核的汇编代码是根据as命令指定的格式编写的,那理论上来说,as的官方文档中应该有at&t风格的汇编指令的相关描述。

  可惜并没有。

  这种情况下,当我们在看linux内核的汇编代码时,只能通过阅读在网上找到的一些零散的at&t风格的汇编文档,以此来尝试理解内核逻辑。

  但很多时候,这些文档并不能给出一个精准全面的解释,致使我们有时无法真正理解内核代码的用意。

  那到哪里才能找到最精确,最全面的汇编指令相关解释呢?

  下面我们来说个方法。

  我们知道,不管是at&t风格,还是intel风格,最终这些汇编代码都会被编译成cpu可识别的二进制编码的机器指令,这个是唯一的,是不会随着汇编代码写法风格的改变而改变的。

  对于x86型的cpu来说,其可识别的汇编指令,及该指令对应的编码格式的最权威最详细的文档介绍,莫过于 Intel® 64 and IA-32 Architectures Software Developer’s Manual 文档了,其官方地址如下:

  https://software.intel.com/sites/default/files/managed/39/c5/325462-sdm-vol-1-2abcd-3abcd.pdf

  在我们阅读linux内核代码的过程中,当遇到有疑问的at&t风格的汇编指令时,我们只需要查看该汇编指令编译后的二进制格式的机器指令,然后通过这些机器指令数据,在上面的intel sdm文档中找到对应的intel汇编指令,这样我们就算是找到了该at&t风格的汇编指令最精确最权威的定义了。

  我们举个例子看下。

  如果没有使用boot loader,比如grub,而是直接启动编译好的linux内核,执行的第一条汇编指令是ljmp:

  假设我们对这条指令有些疑问,想看下其具体的文档说明,下面来看下要怎么做。

  首先,我们用objdump反编译下这部分代码,查看其编译后的机器指令是什么样的:

  $ objdump -S arch/x86/boot/setup.elf

   more其输出如下:

  linux内核编译后的可运行文件是bzImage,而setup.elf正是bzImage文件的开始部分,所以我们上面是objdump的setup.elf,而不是bzImage。

  我们用以下命令再确认下setup.elf确实是bzImage的开始部分:

  对比以上两个图片,我们会发现它们的字节顺序及内容都是一样的,所以确定了我们上面的说法。

  我们再继续看第一个图片中的反编译内容。

  该内容中,前两个字节分别是4d 5a,其对应为内核代码中MZ_MAGIC宏的定义:

  // include/linux/pe.h#define MZ_MAGIC 0x5a4d /* "MZ" */由上可见,MZ_MAGIC宏的值是0x5a4d,这个和我们反编译出来的字节顺序正好相反,这说明我们用的机器是小端模式。

  接下来再看ljmp指令对应的字节内容:ea 07 00 c0 07。

  由intel sdm文档可知,ea 对应的汇编指令可能有下面两种情况(选中行):

  我们再对应看下文档中描述的 ea 后面的 cd 和 cp 的定义:

  由上可知,cd 和 cp 分别表示该汇编指令(ea)后会有4个字节或6个字节的操作数。

  结合我们上面反编译后的ljmp指令的字节数据 ea 07 00 c0 07,以及内核初始是在16位的real-address mode下执行,我们可以最终得到结论,该 ljmp 指令对应的就是上面 ea cd 代表的那行 intel 汇编指令 JMP ptr16:16。

  该指令表示其是一次 far jump,看下其更详细的描述:

  由上可知,我们要jump到的目标,是由segment selector和offset组成的一个4个字节的数值。

  再结合最开始提到的,这条ljmp指令的原始代码是 ljmp $BOOTSEG, $start2,我们可根据BOOTSEG的值0x07C0和start2的值 0x0007(由反汇编结果得到),最终拼出一个4个byte组成的值 07 C0 00 07。

  又由于我们是小端模式,所以该值在存储时要把顺序反过来,最终值为 07 00 C0 07。

  再加上ljmp对应的值 ea,最终这条ljmp指令编译后的字节顺序应该为 ea 07 00 c0 07,对比一下上面反汇编的字节顺序:

  是不是刚好一样。这就进一步确认了,我们找到的ljmp对应的intel汇编指令是正确的。

  通过这种方式,我们就可以找到任意at&t风格的汇编指令最权威,最详尽的描述了。

标签: 云平台