vue 实现网站开发,网站流量利用,win10系统可以做网站搭建,海口澄迈县建设局网站文章目录 gcc的预处理#xff0c;不进行编译、汇编或链接预处理编译汇编 8.8.2 ATT语法与英特尔语法8.8.3操作码命名8.8.4寄存器命名8.8.5操作码前缀8.8.6内存引用8.8.7跳转指令的处理8.8.8浮点8.8.9写入16位代码8.8.10笔记 gcc的预处理#xff0c;不进行编译、汇编或链… 文章目录 gcc的预处理不进行编译、汇编或链接预处理编译汇编 8.8.2 ATT语法与英特尔语法8.8.3操作码命名8.8.4寄存器命名8.8.5操作码前缀8.8.6内存引用8.8.7跳转指令的处理8.8.8浮点8.8.9写入16位代码8.8.10笔记 gcc的预处理不进行编译、汇编或链接
gcc选项-E 仅作预处理不进行编译、汇编或链接。-S 编译到汇编语言不进行汇编和链接-c 编译、汇编到目标代码不进行链接。-o 文件 输出到 文件。-pie 生成动态链接的位置无关可执行文件。-shared 生成一个共享库。-x 语言 指定其后输入文件的语言。允许的语言包括c、c、assembler、none‘none’意味着恢复默认行为即根据文件的扩展名猜测源文件的语言。[haobogon qemu-demo]$ gcc -v -c test.S -o test.o
使用内建 specs。
COLLECT_GCCgcc
OFFLOAD_TARGET_NAMESnvptx-none
OFFLOAD_TARGET_DEFAULT1
目标x86_64-redhat-linux
配置为../configure --enable-bootstrap --enable-host-pie --enable-host-bind-now --enable-languagesc,c,fortran,lto --prefix/usr --mandir/usr/share/man --infodir/usr/share/info --with-bugurlhttp://bugzilla.redhat.com/bugzilla --enable-shared --enable-threadsposix --enable-checkingrelease --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-stylegnu --enable-offload-targetsnvptx-none --without-cuda-driver --enable-gnu-indirect-function --enable-cet --with-tunegeneric --with-arch_64x86-64-v2 --with-arch_32x86-64 --buildx86_64-redhat-linux --with-build-configbootstrap-lto --enable-link-serialization1
线程模型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 -mtunegeneric -marchx86-64-v2/usr/libexec/gcc/x86_64-redhat-linux/11/cc1 -E -lang-asm -quiet -v test.S -mtunegeneric -marchx86-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 -mtunegeneric -marchx86-64-v2as -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 -mtunegeneric -marchx86-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 ATT语法与英特尔语法
ATT 汇编语法通常称为 GAS 语法(GNU as汇编器的语法)。保持与gcc的输出的兼容性。
为了保持与gcc的输出的兼容性as支持ATT System V/386汇编程序语法。这与英特尔的语法大不相同。我们之所以提到这些差异是因为几乎所有80386文档都只使用了Intel语法。这两种语法之间的显著区别在于
ATT立即数操作数前面是“$”英特尔立即数操作数不受限制英特尔的“push 4”是ATT的“pushl $4”。 ATT寄存器操作数前面有“%”英特尔寄存器操作数不受限制。 ATT绝对与PC相对跳转/调用操作数的前缀为“*”它们在英特尔语法中是不受限制的。
操作ATTINTEL立即数操作pushl $4push 4寄存器操作pushl %axpush ax绝对跳转/调用操作jmp *0x100;call *0x100jmp 0x100; call 0x100
ATT和Intel语法对源操作数和目标操作数使用相反的顺序。英特尔的“add eax4”就是“addl $4%eax”。为了与以前的Unix汇编程序兼容保留了“sourcedest”约定。
操作ATTINTEL源操作数和目标操作数addl $4%eaxadd eax4【source dest】【dest source】
将4放入寄存器eax中。
在ATT语法中内存操作数的大小由操作码名称的最后一个字符决定。操作码后缀’b’、w’和’l’指定字节8位、字16位和长32位内存引用。英特尔语法通过在内存操作数而不是操作码本身前面加上“byte ptr”、“word ptr”和“dword ptr”来实现这一点。因此英特尔的“mov albyte ptr foo”在ATT语法中是“movb foo%al”。
操作ATTINTEL内存操作数的大小movb foo%almov albyte ptr foo
将一个字节大小类型的foo变量放入al寄存器中。
立即形式的长跳转和调用在ATT语法中是“lcall/ljmp $section, $offset”英特尔的语法是“call/jmp far section:offset”。此外远返回指令是ATT语法中的“lret $stack adjust”英特尔的语法是“ret far stack-adjust”。ATT汇编程序不支持多节(section)程序。Unix风格的系统期望所有程序都是单个sections。
操作ATTINTEL立即形式的长跳转和调用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”。请注意这与ATT Unix汇编程序不兼容后者认为缺少操作码后缀意味着操作数大小较长。这种不兼容性不会影响编译器输出因为编译器总是显式指定操作码后缀。 在ATT和Intel格式中几乎所有操作码都有相同的名称。也有一些例外。符号扩展和零扩展指令需要两种大小来指定它们。它们需要一个大小来进行签名/zero-extend-from和一个大小to zero-extend to。这是通过在ATT语法中使用两个操作码后缀来实现的。符号扩展和零扩展的基本名称是“movs…”和“movz…”在ATT语法中英特尔语法中的’vsx’和’movzx’。这个操作码后缀被附加到这个基本名称上从后缀在到后缀之前。因此“movsbl%al%edx”是ATT的语法表示“移动符号从%al扩展到%edx。”因此可能的后缀有“bl”从字节到长、“bw”从不字节到单词和“wl”从单词到长。 英特尔语法转换指令 •“cbw”–将“%l”中的字节符号扩展到“%ax”中的字 •“cwde”–将“%ax”中的扩展字符号化为“%eax”中的long •“cwd”–对“%ax”中的单词进行符号扩展使其在“%dx:%ax”中变长 •“cdq”–将“%eax”中的双字符号扩展到“%edx:%eax”的quad 在ATT命名中称为“cbtw”、“cwtl”、“cwtd”和“cltd”。as接受这些指令的任一命名。 远调用/跳转指令在ATT语法中为“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]被翻译成ATT语法
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手册。请注意ATT语法中的节(section)重写前面必须有一个“%”。如果指定与默认section寄存器一致的section重写则不会输出任何section寄存器重写前缀来汇编给定指令。因此可以指定区段重写来强调哪个区段寄存器用于给定的内存操作数。
以下是英特尔和ATT风格内存参考的一些示例
ATT: -4(%ebp), Intel: [ebp - 4]base为“%ebp”disp为“-4”。缺少section使用了默认section“%ss”用于以“%ebp”作为base寄存器进行寻址。index和scale都不见了。
ATT: foo(,%eax,4), Intel: [foo eax*4]index为“%eax”按scale 4缩放disp是“foo”。缺少所有其他字段。此处的section寄存器默认为“%d”。
ATT: foo(,1); Intel [foo]这将使用“foo”指向的值作为内存操作数。请注意base和index都丢失了但只有一个“”。这是一个句法上的例外。
ATT: %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不使用它们则可能会收到错误消息和不正确的代码。ATT 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%st1”等效于“fstl %st%s11”。
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’”。