x86-64汇编基础(x64)

Last Updated: 2023-11-23 15:21:37 Thursday

-- TOC --

本文汇总一些个人学习x86-64(x64或AMD64)汇编过程中,遇到的一些基础知识。内容略显零乱,但都是干货!

汇编语法

一个烦人的现实,x86-64汇编代码没有统一的语法格式!

AT&T语法,寄存器前加%号,立即数前加$号,指令后面还有指示操作数宽度的后缀,比如movl,movq等。src在左,dst在右,例如:

movq src, dst

Intel语法,寄存器和立即数前没有prefix,指令后也没有suffix,但地址前都有说明按多少字节访问地址的信息(cast operator),比如byte ptrword ptr,或dword ptr等等。src在右,dst在左,例如:

mov DWORD PTR [dst], src

如果用gas编译intel语法的汇编代码文件,需要在源文件中使用指令.intel_syntax noprefix,见后文。

objdump -d默认输出AT&T语法,使用-M intel参数就可以切换到Intel语法。(详解objdump命令

Intel语法用[]表示地址,ATT语法用()表示。

In Intel syntax the base register is enclosed in [ and ] where as in AT&T they change to ( and ). Additionally, in Intel syntax an indirect memory reference is like section:[base+index*scale+disp], which changes to section:disp(base,index,scale) in AT&T。(这是最一般的格式,scale只能取1,2,4或8

One point to bear in mind is that, when a constant is used for disp/scale, $ shouldn’t be prefixed。(disp或scale使用立即数时,不需要$前缀)

个人倾向使用Intel语法,因为Intel语法更符合C语言风格。可这个世界很复杂,除了AT&T语法,还有nasm语法...

Cast Operator(Intel)

寻址

movl (addr), %eax,将addr所在位置的4字节存入%eax。label都是addr!

movl data_items(,%edi,4), %eax,地址为%edi*4+dataitems

例如movl 4(%eax), %ebx,用于访问结构体成员比较方便,例如一个结构体的基地址保存在eax寄存器中,其中一个成员在结构体内偏移量是4字节,要把这个成员读上来就可以用这条指令。

或者movl (%eax), %ebx,把eax寄存器的值看作地址,把这个地址处的32位数传送到ebx寄存器。有人说这叫间接寻址

举例:

# 把立即数4存入rbp-4这个地址,movl,后缀l表示long,4bytes的DWORD
movl $4, -4(%rbp)
+------------------------------+------------------------------------+
|       Intel Code             |      AT&T Code                     |
+------------------------------+------------------------------------+
| mov     eax,1                |  movl    $1,%eax                   |   
| mov     ebx,0ffh             |  movl    $0xff,%ebx                |   
| int     80h                  |  int     $0x80                     |   
| mov     ebx, eax             |  movl    %eax, %ebx                |
| mov     eax,[ecx]            |  movl    (%ecx),%eax               |
| mov     eax,[ebx+3]          |  movl    3(%ebx),%eax              | 
| mov     eax,[ebx+20h]        |  movl    0x20(%ebx),%eax           |
| add     eax,[ebx+ecx*2h]     |  addl    (%ebx,%ecx,0x2),%eax      |
| lea     eax,[ebx+ecx]        |  leal    (%ebx,%ecx),%eax          |
| sub     eax,[ebx+ecx*4h-20h] |  subl    -0x20(%ebx,%ecx,0x4),%eax |

将一个汇编代码中label的地址赋给register:
| mov     rax, OFFSET msg      |  mov     $msg, rax                 |
+------------------------------+------------------------------------+

汇编器指令(GAS)

汇编程序中以.开头的名称不是指令,不会被翻译成机器码,它们是给汇编器一些特殊的指示,称为汇编器指令。

.data,定义data section,保存程序的全局和静态数据,可读可写。

.text,定义text section,保存代码,只读和可执行。

.section .rodata,定义只读数据区(必须带上.section前缀)。

定义section的一般格式如下:

.section section_name [,"flags" [,Type]]

.text,.data,.rodata这三个名称的section,都默认的flags和type,因此无需显示指定。查看object或executable文件的sections,请参考readelf命令,有对应的type和flags信息。

.globl指示告诉汇编器这个符号是个全局符号。也可以使用.global,这个更好!

.extern,指定外部符号。

.section .data
data_items:
    .long 3,67,34,222,45,75,54,34,44,33,22,248,66,0

上例中,数组开头有个symbol叫做data_items,汇编器会把数组的首地址作为data_items符号所代表的地址,data_items类似于C中的数组名。data_items这个符号没有.global声明,因为它只在这个汇编程序内部使用,链接器不需要知道这个名字的存在。

.type,用来定义符号的类型。

The ".type" directive is used in assembly language to declare the type of a symbol or label. It is typically used in conjunction with the symbol's name to provide additional information to the assembler or linker about the symbol's properties.

比如,申明一个全局函数接口:

.global funcname
.type funcname, @function

定义一个全局变量:

.global my_variable
.type my_variable, @object
my_variable:
    .word 42

.ascii,例: .ascii "Hello World",声明了11个数,取值为相应字符的ASCII码,末尾是没有'\0'字符。

.data  
msg:  
    .ascii "Hello world, hello AT&T asm!\n"  
    len = . - msg  

len符号代表一个常量,它的值由当前地址(.)减去符号msg所代表的地址得到,就是字符串的长度。用$len表示len的值,Intel语法就直接使用len

.asciz,自动在末尾增加\0字符。

    .asciz "JNZ"    # 插入4个字节: 0x4A 0x4E 0x5A 0x00`

.balign指令(Intel)用来对齐地址,后面跟2的幂次,比如:

    .balign 8   # 8字节对齐,Intel语法

对应的AT&T语法是:

    .align 8    # 8字节对齐,AT&T语法

下面是三种定义常量的语法,这些常量在汇编代码中,就是简单替换:

.set AA, 100
.equ BB, 200
CC = 300

.size,说明一个symbol的size大小。貌似手搓的汇编用不上这条指令。

The ".size" directive is used in assembly language to specify the size of a symbol or label. It is typically used to provide information to the assembler or linker about the size of a data object, function, or other symbol.

funcname:
    ...
    ret
    .size funcname, .-funcname  # 用减法计算size

用as编译intel语法代码

为什么我比较喜欢Intel格式的汇编,是因为Intel很强大吗?不仅如此,Intel格式语法看着更符合C代码,比如目的在左,源在右。

示例代码:

.intel_syntax noprefix

.LC2:
        .string "%d %d %.2f %0.2f %c %d\n"

.section .text
.global main
main:
        sub     rsp, 8
        mov     ecx, 101
        mov     edx, 1
        xor     esi, esi
        mov     r8d, 2
        mov     edi, OFFSET FLAT:.LC2
        mov     eax, 2
        movsd   xmm1, QWORD PTR .LC0[rip]
        movsd   xmm0, QWORD PTR .LC1[rip]
        call    printf
        mov     eax, 7
        add     rsp, 8
        ret
.LC0:
        .long   0
        .long   1074921472
.LC1:
        .long   1610612736
        .long   1073899110

确保这段代码可以被as正常编译,就是.intel_syntax noprefix这行代码,它指示as采用intel语法进行汇编!而且,noprefix是必须的,如果没有它,就表示需要指定各种prefix,即%指定寄存器,$表达立即数。

编译:

$ gcc test.s -o test

可以先用as编译成.o文件,再用ld链接,但手动调用ld时,设置参数会比较麻烦。

$ as test.s -o test.o
$ ld ...

就算是在gcc命令行使用-masm=intel,也需要申明.intel_syntax noprefix这行代码,否则你就需要写带上prefix的Intel语法的汇编(貌似dst和src的位置还是ATT风格的)!但是我在测试使用这个命令行参数编译inline asm的时候,确可以正常编译。嗨.....as没有-masm=intel这个参数!

as同时还有一个.att_syntax指令,切换回默认的ATT风格。

操作数(Operands)

汇编指令操作数是必须的,要么显式指定,要么隐式的被指令强行指定。

Most x86-32 instructions use operands, which designate the specific values that an instruction will act upon. Nearly all instructions require one or more source operands along with a single destination operand. Most instructions also require the programmer to explicitly specify the source and destination operands. There are, however, a number of instructions where the operands are either implicitly specified or forced by the instruction.

有三种操作数类型:

指令支持源或目的operand为memory,但是源或目的不能同时为memory。

There are three basic types of operands: immediate, register, and memory. An immediate operand is a constant value that is encoded as part of the instruction. These are typically used to specify constant arithmetic, logical, or offset values. Only source operands can be used as immediate operands. Register operands are contained in a general-purpose register. A memory operand specifies a location in memory, which can contain any of the data types described earlier in this chapter. An instruction can specify either the source or destination operand as a memory operand, but not both.

Conditional Codes

很多汇编指令中,都包含conditional codes,比如jcccmovccsetcc

助记符 含义
A Above
E Equal,当PE时,Even
B Below
L Less
N 如果只有两个字母,表示Not,如果是3个字母,Neight
Z Zero
G Greater
S Sign
C Carry
O Overflow,当PO时,Odd
P Parity

A和B用于unsigned integer,G和L用于signed integer。

Condition-codes containing the words above and below are employed for unsigned-integer operands, while the words greater and less are used for signed-integer operands.

cmovcc指令的出现,是为了在某些时候干掉branch,加快CPU执行指令的速度。

Intel和AT&T语法中,不同名称的相同指令

绝大多数面向80386的AT&T汇编指令与Intel格式的汇编指令都是相同的,带符号扩展指令和补零扩展指令则是仅有的不同格式指令。

intel att(nosuffix) comments
movsx movs signed extension
movzx movz zero extension
cbw cbtw Sign-extend AL into AX
cwde cwtl Sign-extend AX into EAX
cdqe cltq Sign-extend EAX into RAX
cwd cwtd Sign-extend AX into DX:AX
cdq cltd Sign-extend EAX into EDX:EAX
cqo cqto Sign-extend RAX into RDX:RAX

本文链接:https://cs.pynote.net/hd/asm/202203071/

-- EOF --

-- MORE --