AT&T汇编与GCC内嵌汇编语法

讨论AT&T的汇编语法,以及GCC的内嵌汇编语法。0.3.2 Syntax 1.寄存器引用引用寄存器要在寄存器号前加百分号%,如“movl %eax, %ebx”。80386有如下寄存器:8个32-bit寄存器 %eax,%ebx,%ecx,%edx,%edi,%esi,%ebp,%esp; 8个16-bit寄存器,它们事实上是上面8个32-bit寄存器的低16位:%ax,%bx,%cx,%dx,%di,%si,%bp,%sp; 8个8-bit寄存器:%ah,%al,%bh,%bl,%ch,%cl,%dh,%dl。它们事实上是寄存器%ax,%bx,%cx,%dx的高8位和低8位; 6个段寄存器:%cs(code),%ds(data),%ss(stack), %es,%fs,%gs; 3个控制寄存器:%cr0,%cr2,%cr3; 6个debug寄存器:%db0,%db1,%db2,%db3,%db6,%db7; 2个测试寄存器:%tr6,%tr7; 8个浮点寄存器栈:%st(0),%st(1),%st(2),%st(3),%st(4),%st(5),%st(6),%st(7)。2. 操作数顺序操作数排列是从源(左)到目的(右),如“movl %eax(源), %ebx(目的)”3. 立即数使用立即数,要在数前面加符号$, 如“movl $0x04, %ebx”或者:para = 0x04movl $para, %ebx指令执行的结果是将立即数04h装入寄存器ebx。4. 符号常数符号常数直接引用如value: .long 0x12a3f2demovl value , %ebx指令执行的结果是将常数0x12a3f2de装入寄存器ebx。引用符号地址在符号前加符号$, 如“movl $value, % ebx”则是将符号value的地址装入寄存器ebx。5. 操作数的长度操作数的长度用加在指令后的符号表示b(byte, 8-bit), w(word, 16-bits), l(long, 32-bits),如“movb %al, %bl”,“movw %ax, %bx”,“movl %eax, %ebx ”。如果没有指定操作数长度的话,编译器将按照目标操作数的长度来设置。比如指令“mov %ax, %bx”,由于目标操作数bx的长度为word,那么编译器将把此指令等同于“movw %ax, %bx”。同样道理,指令“mov $4, %ebx”等同于指令“movl $4, %ebx”,“push %al”等同于“pushb %al”。对于没有指定操作数长度,但编译器又无法猜测的指令,编译器将会报错,比如指令“push $4”。6. 符号扩展和零扩展指令绝大多数面向80386的AT&T汇编指令与Intel格式的汇编指令都是相同的,符号扩展指令和零扩展指令则是仅有的不同格式指令。符号扩展指令和零扩展指令需要指定源操作数长度和目的操作数长度,即使在某些指令中这些操作数是隐含的。在AT& T语法中,符号扩展和零扩展指令的格式为,基本部分"movs"和"movz"(对应Intel语法的movsx和movzx),后面跟上源操作数长度和目的操作数长度。movsbl意味着movs (from)byte (to)long;movbw意味着movs (from)byte (to)word;movswl意味着movs (from)word (to)long。对于movz指令也一样。比如指令“movsbl %al, %edx”意味着将al寄存器的内容进行符号扩展后放置到edx寄存器中。其它的Intel格式的符号扩展指令还有:cbw -- sign-extend byte in %al to word in %ax; cwde -- sign-extend word in %ax to long in %eax; cwd -- sign-extend word in %ax to long in %dx:%ax; cdq -- sign-extend dword in %eax to quad in %edx:%eax;对应的AT&T语法的指令为cbtw,cwtl,cwtd,cltd。7. 调用和跳转指令段内调用和跳转指令为"call","ret"和"jmp",段间调用和跳转指令为"lcall","lret"和"ljmp"。段间调用和跳转指令的格式为“lcall/ljmp $SECTION, $OFFSET”,而段间返回指令则为“lret $STACK-ADJUST”。8. 前缀操作码前缀被用在下列的情况:字符串重复操作指令(rep,repne); 指定被操作的段(cs,ds,ss,es,fs,gs); 进行总线加锁(lock); 指定地址和操作的大小(data16,addr16);在AT&T汇编语法中,操作码前缀通常被单独放在一行,后面不跟任何操作数。例如,对于重复scas指令,其写法为:repnescas上述操作码前缀的意义和用法如下:指定被操作的段前缀为cs,ds,ss,es,fs,和gs。在AT&T语法中,只需要按照section:memory-operand的格式就指定了相应的段前缀。比如:lcall %cs:realmode_swtch 操作数/地址大小前缀是“data16”和"addr16",它们被用来在32-bit操作数/地址代码中指定16-bit的操作数/地址。 总线加锁前缀“lock”,它是为了在多处理器环境中,保证在当前指令执行期间禁止一切中断。这个前缀仅仅对ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG,DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD,XCHG指令有效,如果将Lock前缀用在其它指令之前,将会引起异常。 字符串重复操作前缀"rep","repe","repne"用来让字符串操作重复“%ecx”次。9. 内存引用Intel语法的间接内存引用的格式为:section:[base+index*scale+displacement]而在AT&T语法中对应的形式为:section:displacement(base,index,scale)其中,base和index是任意的32-bit base和index寄存器。scale可以取值1,2,4,8。如果不指定scale值,则默认值为1。section可以指定任意的段寄存器作为段前缀,默认的段寄存器在不同的情况下不一样。如果你在指令中指定了默认的段前缀,则编译器在目标代码中不会产生此段前缀代码。下面是一些例子:-4(%ebp):base=%ebp,displacement=-4,section没有指定,由于base=%ebp,所以默认的section=%ss,index,scale没有指定,则index为0。foo(,%eax,4):index=%eax,scale=4,displacement=foo。其它域没有指定。这里默认的section=%ds。foo(,1):这个表达式引用的是指针foo指向的地址所存放的值。注意这个表达式中没有base和index,并且只有一个逗号,这是一种异常语法,但却合法。%gs:foo:这个表达式引用的是放置于%gs段里变量foo的值。如果call和jump操作在操作数前指定前缀“*”,则表示是一个绝对地址调用/跳转,也就是说jmp/call指令指定的是一个绝对地址。如果没有指定"*",则操作数是一个相对地址。任何指令如果其操作数是一个内存操作,则指令必须指定它的操作尺寸(byte,word,long),也就是说必须带有指令后缀(b,w,l)。.3 GCC Inline ASMGCC 支持在C/C++代码中嵌入汇编代码,这些汇编代码被称作GCC Inline ASM——GCC内联汇编。这是一个非常有用的功能,有利于我们将一些C/C++语法无法表达的指令直接潜入C/C++代码中,另外也允许我们直接写 C/C++代码中使用汇编编写简洁高效的代码。1.基本内联汇编GCC中基本的内联汇编非常易懂,我们先来看两个简单的例子:__asm__("movl %esp,%eax"); // 看起来很熟悉吧!或者是__asm__("movl $1,%eax // SYS_exitxor %ebx,%ebxint $0x80");或__asm__("movl $1,%eax\r\t" \"xor %ebx,%ebx\r\t" \"int $0x80" \);基本内联汇编的格式是__asm__ __volatile__("Instruction List");1、__asm____asm__是GCC关键字asm的宏定义:#define __asm__ asm__asm__或asm用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都是以它开头的,是必不可少的。2、Instruction ListInstruction List是汇编指令序列。它可以是空的,比如:__asm__ __volatile__(""); 或__asm__ ("");都是完全合法的内联汇编表达式,只不过这两条语句没有什么意义。但并非所有Instruction List为空的内联汇编表达式都是没有意义的,比如:__asm__ ("":::"memory"); 就非常有意义,它向GCC声明:“我对内存作了改动”,GCC在编译的时候,会将此因素考虑进去。我们看一看下面这个例子:$ cat example1.cint main(int __argc, char* __argv[]) { int* __p = (int*)__argc; (*__p) = 9999; //__asm__("":::"memory"); if((*__p) == 9999) return 5; return (*__p); }在这段代码中,那条内联汇编是被注释掉的。在这条内联汇编之前,内存指针__p所指向的内存被赋值为9999,随即在内联汇编之后,一条if语句判断__p 所指向的内存与9999是否相等。很明显,它们是相等的。GCC在优化编译的时候能够很聪明的发现这一点。我们使用下面的命令行对其进行编译:$ gcc -O -S example1.c选项-O表示优化编译,我们还可以指定优化等级,比如-O2表示优化等级为2;选项-S表示将C/C++源文件编译为汇编文件,文件名和C/C++文件一样,只不过扩展名由.c变为.s。我们来查看一下被放在example1.s中的编译结果,我们这里仅仅列出了使用gcc 2.96在redhat 7.3上编译后的相关函数部分汇编代码。为了保持清晰性,无关的其它代码未被列出。$ cat example1.smain: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax # int* __p = (int*)__argcmovl $9999, (%eax) # (*__p) = 9999 movl $5, %eax # return 5popl %ebp ret参照一下C源码和编译出的汇编代码,我们会发现汇编代码中,没有if语句相关的代码,而是在赋值语句(*__p)=9999后直接return 5;这是因为GCC认为在(*__p)被赋值之后,在if语句之前没有任何改变(*__p)内容的操作,所以那条if语句的判断条件(*__p) == 9999肯定是为true的,所以GCC就不再生成相关代码,而是直接根据为true的条件生成return 5的汇编代码(GCC使用eax作为保存返回值的寄存器)。我们现在将example1.c中内联汇编的注释去掉,重新编译,然后看一下相关的编译结果。$ gcc -O -S example1.c$ cat example1.smain: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax # int* __p = (int*)__argcmovl $9999, (%eax) # (*__p) = 9999#APP # __asm__("":::"memory")#NO_APPcmpl $9999, (%eax) # (*__p) == 9999 ?jne .L3 # false movl $5, %eax # true, return 5 jmp .L2 .p2align 2 .L3: movl (%eax), %eax .L2: popl %ebp ret由于内联汇编语句__asm__("":::"memory"
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
setjmp是一个C语言库函数,用于实现非局部跳转(longjmp)。在GCC汇编中,可以通过使用汇编嵌入(inline assembly)的方式来调用setjmp。 下面是一个示例代码: ```c #include <setjmp.h> jmp_buf jump_buffer; int main() { int r = setjmp(jump_buffer); if (r == 0) { // 跳转到这里 } else { // 在这里处理跳转返回 } } ``` 以上的代码通过setjmp将程序的控制流程保存在了jump_buffer中,并返回了一个值r。通过在不同的条件下调用longjmp,可以在程序的不同位置跳转到jump_buffer所指向的位置。 在汇编中,可以通过使用.x86_64特殊标志来指示应该使用64位模式来编译代码。以下是一个使用汇编嵌入的setjmp示例代码: ```c #include <setjmp.h> jmp_buf jump_buffer; int my_setjmp() { int r; asm volatile ("movq %%rsp, %0\n\t" // 保存rsp "movq %1, %%rsp\n\t" // 跳转到jump_buffer "xorl %%eax, %%eax\n\t" "movl $1, %%eax\n\t" "jmp save_regs\n" "save_regs:" "pushq %%rbp\n\t" // 保存rbp "movq %%rsp, %%rbp\n\t" // 保存rsp到rbp "movq %%rax, (%1)\n\t" // 保存返回值 "lea 8(%%rbp), %%rsp\n\t" // 跳过返回地址恢复栈指针 "popq %%rax\n\t" // 恢复rax "popq %%rbp\n\t" // 恢复rbp "movl $0, %%eax\n\t" "ret\n\t" : "=m" (jump_buffer), "=m" (r) : : "memory"); return r; } void my_longjmp(int r) { asm volatile ("movq %0, %%rax\n\t" "jmp restore_regs\n" "restore_regs:" "movq %%rax, %%rsp\n\t" "popq %%rbp\n\t" "xorl %%eax, %%eax\n\t" "movl %1, %%eax\n\t" "jmp *%2\n\t" : : "m" (jump_buffer), "m" (r), "m" (jump_buffer[0]) : "memory"); } ``` 上述代码中实现了自定义的setjmp和longjmp函数。在my_setjmp中,使用汇编指令将当前rsp指针保存到jump_buffer中,并返回值1。在my_longjmp中,将jmp_buf指针中保存的rsp值恢复,并设置返回值。具体实现中,需要注意图示的栈帧结构,并使用volatile标记来强制GCC不要将某些寄存器的值保存在寄存器中而是强制保存到内存。同时,在GCC优化开启的情况下,需要使用"memory"约束来告知编译器此区域存储内容被内联汇编代码更改了。 这样,就可以在GCC汇编中使用setjmp和longjmp实现非局部跳转。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值