80386 AT&T汇编语法

gcc的预处理,不进行编译、汇编或链接

gcc选项
  -E                       仅作预处理,不进行编译、汇编或链接。
  -S                       编译到汇编语言,不进行汇编和链接,
  -c                       编译、汇编到目标代码,不进行链接。
  -o <文件>                输出到 <文件>-pie                     生成动态链接的位置无关可执行文件。
  -shared                  生成一个共享库。
  -x <语言>                指定其后输入文件的语言。
                           允许的语言包括:c、c++、assembler、none
                           ‘none’意味着恢复默认行为,即根据文件的扩展名猜测
                           源文件的语言。
[hao@bogon qemu-demo]$ gcc -v -c test.S -o test.o
使用内建 specs。
COLLECT_GCC=gcc
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
目标:x86_64-redhat-linux
配置为:../configure --enable-bootstrap --enable-host-pie --enable-host-bind-now --enable-languages=c,c++,fortran,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-gcc-major-version-only --enable-plugin --enable-initfini-array --without-isl --enable-multilib --with-linker-hash-style=gnu --enable-offload-targets=nvptx-none --without-cuda-driver --enable-gnu-indirect-function --enable-cet --with-tune=generic --with-arch_64=x86-64-v2 --with-arch_32=x86-64 --build=x86_64-redhat-linux --with-build-config=bootstrap-lto --enable-link-serialization=1
线程模型:posix
Supported LTO compression algorithms: zlib zstd
gcc 版本 11.4.1 20231218 (Red Hat 11.4.1-3) (GCC)
COLLECT_GCC_OPTIONS='-v' '-c' '-o' 'test.o' '-mtune=generic' '-march=x86-64-v2'
 /usr/libexec/gcc/x86_64-redhat-linux/11/cc1 -E -lang-asm -quiet -v test.S -mtune=generic -march=x86-64-v2 -fno-directives-only -o /tmp/ccZppfRe.s
忽略不存在的目录“/usr/lib/gcc/x86_64-redhat-linux/11/include-fixed”
忽略不存在的目录“/usr/lib/gcc/x86_64-redhat-linux/11/../../../../x86_64-redhat-linux/include”
#include "..." 搜索从这里开始:
#include <...> 搜索从这里开始:
 /usr/lib/gcc/x86_64-redhat-linux/11/include
 /usr/local/include
 /usr/include
搜索列表结束。
COLLECT_GCC_OPTIONS='-v' '-c' '-o' 'test.o' '-mtune=generic' '-march=x86-64-v2'
 as -v --64 -o test.o /tmp/ccZppfRe.s
GNU assembler version 2.35.2 (x86_64-redhat-linux) using BFD version version 2.35.2-43.el9
COMPILER_PATH=/usr/libexec/gcc/x86_64-redhat-linux/11/:/usr/libexec/gcc/x86_64-redhat-linux/11/:/usr/libexec/gcc/x86_64-redhat-linux/:/usr/lib/gcc/x86_64-redhat-linux/11/:/usr/lib/gcc/x86_64-redhat-linux/
LIBRARY_PATH=/usr/lib/gcc/x86_64-redhat-linux/11/:/usr/lib/gcc/x86_64-redhat-linux/11/../../../../lib64/:/lib/../lib64/:/usr/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/11/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-c' '-o' 'test.o' '-mtune=generic' '-march=x86-64-v2' '-dumpdir' 'test.'

预处理

gcc -E test.c -o test.S

将头文件内容包含到源文件中。生成test.S文件。

编译

gcc -E test.S -o /tmp/ccZppfRe.s

将预处理文件编译成汇编语言文件。

汇编

as --64 -o test.o /tmp/ccZppfRe.s

将汇编语言文件编译成目标文件。

8.8.2 AT&T语法与英特尔语法

AT&T 汇编语法,通常称为 GAS 语法(GNU as汇编器的语法)。保持与gcc的输出的兼容性

为了保持与gcc的输出的兼容性,as支持AT&T System V/386汇编程序语法。这与英特尔的语法大不相同。我们之所以提到这些差异,是因为几乎所有80386文档都只使用了Intel语法。这两种语法之间的显著区别在于:

  • AT&T立即数操作数前面是“$”;英特尔立即数操作数不受限制(英特尔的“push 4”是AT&T的“pushl $4”)。
    AT&T寄存器操作数前面有“%”;英特尔寄存器操作数不受限制。
    AT&T绝对(与PC相对)跳转/调用操作数的前缀为“*”;它们在英特尔语法中是不受限制的。
操作AT&TINTEL
立即数操作pushl $4push 4
寄存器操作pushl %axpush ax
绝对跳转/调用操作jmp *0x100;call *0x100jmp 0x100; call 0x100
  • AT&T和Intel语法对源操作数和目标操作数使用相反的顺序。英特尔的“add eax,4”就是“addl $4,%eax”。为了与以前的Unix汇编程序兼容,保留了“source,dest”约定。
操作AT&TINTEL
源操作数和目标操作数addl $4,%eaxadd eax,4
【source, dest】【dest, source】

将4放入寄存器eax中。

  • 在AT&T语法中,内存操作数的大小由操作码名称的最后一个字符决定。操作码后缀’b’、'w’和’l’指定字节(8位)、字(16位)和长(32位)内存引用。英特尔语法通过在内存操作数(而不是操作码本身)前面加上“byte ptr”、“word ptr”和“dword ptr”来实现这一点。因此,英特尔的“mov al,byte ptr foo”在AT&T语法中是“movb foo,%al”。
操作AT&TINTEL
内存操作数的大小movb foo,%almov al,byte ptr foo

将一个字节大小类型的foo变量放入al寄存器中。

  • 立即形式的长跳转和调用在AT&T语法中是“lcall/ljmp $section, $offset”;英特尔的语法是“call/jmp far section:offset”。此外,远返回指令是AT&T语法中的“lret $stack adjust”;英特尔的语法是“ret far stack-adjust”。
  • AT&T汇编程序不支持多节(section)程序。Unix风格的系统期望所有程序都是单个sections。
操作AT&TINTEL
立即形式的长跳转和调用lcall/ljmp $section, $offsetcall/jmp far section:offset
远返回指令lret $stack adjustret far stack-adjust

8.8.3操作码命名

操作码名称后缀为一个字符修饰符,用于指定操作数的大小。字母“b”、“w”和“l”指定字节、单词和长操作数。如果指令没有指定后缀,并且它不包含内存操作数,则as会尝试根据目标寄存器操作数(约定为最后一个)填充缺少的后缀。因此,“mov%ax,%bx”等效于“movw%ax,%bx”;此外,“movw$1,%bx”等效于“movvw$1,%bx”。请注意,这与AT&T Unix汇编程序不兼容,后者认为缺少操作码后缀意味着操作数大小较长。(这种不兼容性不会影响编译器输出,因为编译器总是显式指定操作码后缀。)
在AT&T和Intel格式中,几乎所有操作码都有相同的名称。也有一些例外。符号扩展和零扩展指令需要两种大小来指定它们。它们需要一个大小来进行签名/zero-extend-from和一个大小to zero-extend to。这是通过在AT&T语法中使用两个操作码后缀来实现的。符号扩展和零扩展的基本名称是“movs…”和“movz…”在AT&T语法中(英特尔语法中的’vsx’和’movzx’)。这个操作码后缀被附加到这个基本名称上,从后缀在到后缀之前。因此,“movsbl%al,%edx”是AT&T的语法,表示“移动符号从%al扩展到%edx。”因此,可能的后缀有“bl”(从字节到长)、“bw”(从不字节到单词)和“wl”(从单词到长)。
英特尔语法转换指令
•“cbw”–将“%l”中的字节符号扩展到“%ax”中的字,
•“cwde”–将“%ax”中的扩展字符号化为“%eax”中的long,
•“cwd”–对“%ax”中的单词进行符号扩展,使其在“%dx:%ax”中变长,
•“cdq”–将“%eax”中的双字符号扩展到“%edx:%eax”的quad,
在AT&T命名中称为“cbtw”、“cwtl”、“cwtd”和“cltd”。as接受这些指令的任一命名。
远调用/跳转指令在AT&T语法中为“lcall”和“ljmp”,但在Intel约定中为“call Far”和“jump Far”。

8.8.4寄存器命名

寄存器操作数总是以“%”为前缀。80386寄存器包括

  • 8个32位寄存器“%eax”(累加器)、“%ebx”、“%ecx”、”%edx“、”%ddi“、”%esi“、”%ebp“(帧指针)和”%esp“(堆栈指针)。
  • 它们的8个16位低端:“%ax”、“%bx”、“%cx”、”%dx“、”%di“、”%si“、“%bp”和”%sp“。
  • 8个8位寄存器:“%ah”、“%al”、“%bh”、“%l”、”%ch“、”%cl“、”%dh“和”%dl“(这些是”%ax“、”%bx“、”%cx“和”%dx“的高字节和低字节)
  • 6段寄存器“%cs”(代码段)、“%ds”(数据段)、”%ss“(堆栈段)、‘%s’、”%fs“和”%gs“。
  • 3个处理器控制寄存器“%cr0”、“%cr2”和“%cr3”。
  • 6个调试寄存器“%db0”、“%db1”、“%db2”、“%db3”、“%tb6”和“%db7”。
  • 2个测试寄存器“%tr6”和“%tr7”。
  • 8浮点寄存器堆栈“%st”,或等效的‘%st(0)’, ‘%st(1)’, ‘%st(2)’,‘%st(3)’, ‘%st(4)’, ‘%st(5)’, ‘%st(6)’, and ‘%st(7)’.。

8.8.5操作码前缀

操作码前缀用于修改以下操作码。它们用于重复字符串指令、提供节(section)重写、执行总线锁定操作以及指定操作数和地址大小(在指令中,通过在通常为32位操作数的操作数前面加上“操作数大小”操作码前缀来指定16位操作数)。操作码前缀通常作为没有操作数的单行指令给出,并且必须直接位于它们所执行的指令之前。例如,“scas”(扫描字符串:scan string)指令重复使用:

	repne 
	scas

以下是操作码前缀列表:

  • 节重写前缀为“cs”、“ds”、“ss”、“es”、“fs”、“gs”。通过为内存引用指定section:memory-operand形式,可以自动添加这些值。
  • 操作数/地址大小前缀“data16”和“addr16”将32位操作数/寻址更改为16位操作数或寻址。请注意,目前还不支持16位寻址模式(即8086和80286寻址模式)。
  • 总线锁定前缀“lock”在执行其前面的指令期间禁止中断。(这仅在特定说明下有效;有关详细信息,请参阅80386手册)。
  • 等待协处理器前缀“wait”等待协处理器完成当前指令。80386/80387组合不应该需要此功能。
  • “rep”、“repe”和“repne”前缀被添加到字符串指令中,使它们重复“%ecx”次。

8.8.6内存引用

Intel语法间接内存引用的形式

section:[base + index*scale + disp]

被翻译成AT&T语法

section:disp(base, index, scale)

其中,base和index是可选的32位base寄存器和index寄存器,disp是可选的位移,scale取值1、2、4和8,乘以索引(index)以计算操作数的地址。如果未指定比例(scale),则比例(scale)为1。节(section)指定内存操作数的可选section寄存器,并且可以覆盖默认的section寄存器(有关section寄存器默认值,请参阅80386手册)。请注意,AT&T语法中的节(section)重写前面必须有一个“%”。如果指定与默认section寄存器一致的section重写,则不会输出任何section寄存器重写前缀来汇编给定指令。因此,可以指定区段重写来强调哪个区段寄存器用于给定的内存操作数。

以下是英特尔和AT&T风格内存参考的一些示例:

AT&T: '-4(%ebp)', Intel: '[ebp - 4]'

base为“%ebp”;disp为“-4”。缺少section,使用了默认section(“%ss”用于以“%ebp”作为base寄存器进行寻址)。index和scale都不见了。

AT&T: 'foo(,%eax,4)', Intel: '[foo + eax*4]'

index为“%eax”(按scale 4缩放);disp是“foo”。缺少所有其他字段。此处的section寄存器默认为“%d”。

AT&T: 'foo(,1)'; Intel '[foo]'

这将使用“foo”指向的值作为内存操作数。请注意,base和index都丢失了,但只有一个“,”。这是一个句法上的例外。

AT&T: '%gs:foo'; Intel 'gs:foo'

这将选择变量“foo”的内容,其中section寄存器section为“%gs”。

绝对(与PC相对)调用和跳转操作数必须以“”为前缀。
如果未指定“
”,则一如既往地为跳转/调用标签选择PC相对寻址。
任何具有内存操作数的指令都必须使用操作码后缀(分别为’b’、‘w’或’l’)指定其大小(字节、字或长)。

8.8.7跳转指令的处理

跳转指令总是经过优化,以使用尽可能小的位移。这是通过每当目标足够接近时使用字节(8位)位移跳跃来实现的。如果字节位移不足,则使用长(32位)位移。我们不支持字(16位)位移跳跃(即在跳跃指令前加上“addr16”操作码前缀),因为80386坚持在添加字位移后将“%eip”屏蔽为16位。
请注意,‘jcxz’、‘jecxz’、‘sloop’、‘loopz’、‘aloope’、'loopnz’和’loopne’指令仅以字节位移形式出现,因此,如果使用这些指令(gcc不使用它们),则可能会收到错误消息(和不正确的代码)。AT&T 80386汇编程序试图通过将“jcxz-foo”扩展到

		jcxz cx_zero 
		jmp cx_nonzero
cx_zero:jmp foo 
cx_nonzero:

8.8.8浮点

支持除压缩BCD之外的所有80387浮点类型。(添加BCD支持可能没有太大困难)。这些数据类型是16位、32位和64位整数,以及单(32位)、双(64位)和扩展(80位)精度浮点。每个支持的类型都有一个操作码后缀和一个与之相关的构造函数。操作码后缀指定操作数的数据类型。构造函数将这些数据类型构建到内存中。
•浮点构造函数是32位、64位和80位格式的“.foat”或“.ssingle”、“.double”和“.tfloat”。这些对应于操作码后缀“s”、“l”和“t”t’代表临时实数,80387仅通过’fldt’(将临时实数加载到堆栈顶部)和’fstpt’(存储临时实数和弹出堆栈)指令支持此格式。
•对于16位、32位和64位整数格式,整数构造函数为“.word”、“.long”或“.int”以及“.fquad”。相应的操作码后缀是’s’(single)、‘l’(long)和’q’(quad)。与临时实数格式一样,64位“q”格式仅存在于“fildq”(将四进制整数加载到堆栈顶部)和“fistpq”(存储四进制整数和弹出堆栈)指令中。
注册到注册操作不需要操作码后缀,因此“fst%st,%st(1)”等效于“fstl %st,%s1(1))”。

8.8.9写入16位代码

虽然GAS通常只编写“纯”32位i386代码,但它对编写在真实模式或16位保护模式代码段中运行的代码的支持有限。为此,在要以16位模式运行的汇编语言指令之前插入“.code16”指令。您可以使用“.code32”指令将GAS切换回编写正常的32位代码。
GAS在16位模式下理解的汇编语言语法与在32位模式下完全相同。任何给定指令的函数在任何模式下都是完全相同的,只要生成的目标代码是在GAS编写它的模式下执行的。因此,例如,“ret”助记符生成32位返回指令,无论它是在16位模式还是32位模式下运行。(如果GAS处于16位模式,它将向指令添加一个操作数大小前缀,以强制其返回32位。)

这意味着,首先,您可以使用gnucc编写要在真实模式或16位保护模式下运行的代码。只需插入语句“asm(“.code16”);”在C源文件的开头,虽然gnu-cc仍将生成32位代码,但GAS将自动添加所有必要的大小前缀,使代码以16位模式运行。当然,由于gnu-cc只写小的模型代码(它不知道如何像原生x86编译器那样将段选择器附加到指针上),所以使用gnu-cc编写的任何16位代码基本上都将限制在64K的地址空间内。此外,由于GAS必须向指令中添加所有额外的地址和操作数大小前缀,因此会导致代码大小和性能损失。

请注意,将GAS置于16位模式并不意味着生成的代码必须在80386之前的16位处理器上运行。要编写在这样的处理器上运行的代码,您必须避免使用任何需要GAS输出地址或操作数大小前缀的32位结构。目前,这将相当困难,因为GAS目前只支持32位寻址模式:当写入16位代码时,它总是为任何使用非寄存器寻址模式的指令输出地址大小前缀。因此,您可以编写在16位处理器上运行的代码,但前提是该代码从不引用内存。

8.8.10笔记

有一些关于“mul”和“imul”指令的诡计值得一提。16位、32位和64位扩展乘法(基本操作码“0xf6”;“mul”的扩展码4和“imul”的扩展名5)只能以一个操作数形式输出。因此,“imul%ebx,%eax”不选择扩展乘法;扩展乘法会阻塞“%dx”寄存器,这会混淆gcc的输出。使用“imul%ebx”获取“%edx:%eax”中的64位乘积。

当第一个操作数是立即数模式表达式,第二个操作数为寄存器时,我们添加了一个双操作数形式的“imul”。这只是一个简写,因此,例如,将“%eax”乘以69,可以使用imul $69,%eax,而不是“imul $69,%eax,%eax’”。

  • 20
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值