Assembly format AT&T and Intel

伍陆柒
中文

Assembly format AT&T and Intel

"CSAPP" is in AT&T format, "Assembly Language Wang Shuang" is in Intel format

Preface

that can be directly recognized and executed by the CPU expressed in binary code. Different CPU architectures have different machine instructions. The assembly instruction is a writing format that is easy to remember for machine instructions. After the assembly instruction is written, it is translated into machine instructions for CPU execution by the assembler. Therefore, assembler is to translate assembly instructions into machine instructions

The same machine instruction can be expressed by different assembly instructions to ensure that there is no error when the assembler is executed. Different assembly instruction formats derive different assembly grammars and each has a corresponding assembler.

With the development of computers, different manufacturers have formed their own assembler languages and have their own assemblers. Different assembly languages may have inconsistent syntax for implementing the same machine instructions

Common assemblers are:

  • GAS (GNU Assembler) , using the AT&T syntax format
  • MASM (Microsoft Macro Assembler) , using the Intel syntax format
  • NASM (Netwide Assembler) , the syntax format used is Intel , but simpler
  • FASM (Flat Assembler)

GAS AT&T syntax format query

MASMT's Intel syntax format query

Grammatical format

Register name

The register name in AT&T needs to be prefixed with % , but Intel does not need it. E.g:

pushl %eax # AT&T 格式
push eax # Intel 格式

Immediate operand

AT&T uses the $ represent an immediate value, while Intel does not need to carry any prefix. E.g:

pushl $1 # AT&T
push 1 # Intel

Operation direction

The positions of source operand and destination operand in AT&T and Intel are exactly opposite.

The AT&T destination operand is to the right of the source operand, and the Intel destination operand is to the left of the source operand. E.g:

addl $1,%eax # AT&T
add eax,1 # Intel

Operation word length

The word length of the AT&T operand is determined by the last letter of the operand, and the suffix and expression word length are as follows:

  • b : byte, 8 bits (bit)
  • w : word, 16 bits
  • l : long, 32 bits

The word length of Intel operands is represented by instruction prefixes such as byte ptr and word ptr E.g:

movb val,%al # AT&T
movl al,byte ptr val # Intel

Absolute transfer and call

AT&T absolute transfer jump and call call operands need to add the * , but Intel does not need

Remote transfer and remote subcall

AT&T remote transfer instruction ljump and remote sub-call instruction lcall , while Intel is jmp far and call far

ljump $section,$offset # AT&T
lcall $section,$offset

jmp far section:offset # Intel
call far section:offset

Corresponding remote return

lret $stack_adjust # AT&T
ret far stack_adjust # Intel

Addressing mode of memory operand

AT&T’s format is section:disp(base,index,scale) , while Intel section:[base + index*scale + disp]

Here are some examples of memory operands:

# AT&T 
movl -4(%ebp),%eax
movl array(,%eax,4),%eax
movw array(%ebx,%eax,4),%cx
movb $4,%fs:(%eax)

# Intel
mov eax,[ebp - 4]
mov eax,[eax*4 + array]
mov cx,[ebx + 4*eax + array]
mov fs:eax,4

Hello World

In the Linux operating system, the simplest method is to use the system calls provided by the kernel. The biggest advantage of this method is that it can communicate directly with the operating system kernel, because there is no need to link libc , nor to use the ELF interpreter, so the code size is small and the execution speed is fast.

Linux is a 32-bit operating system running in protected mode, using flat memory mode. Currently, the most commonly used ELF (Executable and Linkable Format).

An executable program in ELF format is usually divided into the following parts:

  • .text : read-only code area
  • .data : readable and writable data area
  • .bss : data area that can be read and written without initialization

Code area and data area are collectively referred to as section in ELF. According to actual needs, you can use other standard section or add custom section , but an ELF executable program should have at least one .text part.

Here is a program that outputs Hello, world!

# AT&T 格式;hello.s
.data    # 数据段声明
    msg : .string "Hello, world!\\n" # 要输出的字符串
    len = . - msg # 字符串长度
.txt # 代码段声明
.global _start # 指定入口函数
_start: # 在屏幕上显示一个字符串
    movl $len,%edx # 参数3,字符串长度
    movl $msg,%ecx # 参数2,要显示的字符串
    movl $1,%ebx # 参数1,文件描述符(stdout)
    movl $4,%eax # 系统调用号(sys_write)
    int $0x80 # 调用内核功能
        # 退出程序
    movl $0,%ebx # 参数1,退出代码
    movl $1,%eax # 系统调用号(sys_exit)
    int $0x80 # 调用内核功能

When first encountered the assembly code in AT&T format, many programmers thought it was too obscure and difficult to understand. It doesn’t matter. Intel format can also be used to write assembly programs on the Linux platform.

; Intel 格式;hello.asm
section .data ; 数据段声明
    msg db "Hello,world!",0xA ; 要输出的字符串
    len equ $ - msg ; 字符串长度

section .text ; 代码段声明
global _start ; 指定入口函数

_start: ; 在屏幕上显示一个字符串
    mov edx,len    ; 参数3,字符串长度
    mov ecx,msg ; 参数2,要显示的字符串
    mov ebx,1 ; 参数1,文件描述符(stdout)
    mov eax,4 ; 系统调用号(sys_write)
    int 0x80 ; 调用内核功能
        ; 退出程序
    mov ebx,0 ; 参数1,退出代码
    mov eax,1 ; 系统调用号(sys_exit)
    int 0x80    ; 调用内核功能

Although the syntax used by the above two assemblers is completely different, the single function is to call sys_write provided by the Linux kernel to display a string, and then call sys_exit exit the program. In the Linux kernel file include/asm-i386/unistd.h , you can find the definition of all system calls

System call

Even the simplest assembler, it is inevitable to use input, output, exit and other operations. needs to call the service provided by the operating system, that is, the system call . Unless your program only performs mathematical operations such as addition, subtraction, multiplication, and division, it is difficult to avoid using system calls. In fact, the assemblers of various operating systems are very similar except for the system calls.

There are two ways to use system calls under the Linux platform:

  • Use the packaged C library (libc)
  • Directly call through assembly

Among them, the use of system calls through assembly language is the most efficient way to use Linux kernel services, because the final generated program does not need to be linked with any libraries, but directly communicates with the kernel

Like DOS, system calls under Linux are also implemented through interrupts (int 0x80). When the int 80 instruction is executed, the function number of the system call is stored in the register eax, and the parameters passed to the system call must be placed in the registers ebx, ecx, edx, esi, edi in order. When the system call is completed, return The value can be obtained in the register eax

All functions of the system call numbers are in the file /usr/include/bits/syscall.h found, for ease of use, they are made SYS_<name> macros defined such as SYS_write , SYS_exit and so on. For example, the frequently used write is defined as follows:

ssize_t write(int fd, const void *buf, size_t count);
# 该函数的功能最终通过 SYS_write 这一系列调用来实现的
# 根据上面的约定,参数 fd、buf、count 分别存在寄存器 ebx、ecx、edx 中
# 而系统调用号 SYS_write 则放在寄存器 eax 中,当 int 0x80 指令执行完后,返回值可以从寄存器 eax 中获得

at most 5 registers to save the parameter when making a system call. When the parameters required for a system call is greater than 5, the system call function number still needs to be stored in the register eax when the int 0x80 instruction is executed. The difference is that all parameters should be placed in a contiguous memory area (stack space) in turn, and the pointer to this memory area is stored in the register ebx. After the system call is completed, the return value will still be stored in the register eax

Command line parameters

In the Linux operating system, when an executable program is started via the command line, its required parameters will be saved to the stack:

  1. argc
  2. Point to the instruction array argv of each command line parameter
  3. Instruction data envp pointing to environment variables

When writing assembly language programs, many of these parameters need to be processed. The following code shows how to process command line parameters in assembly code:

# args.s
.txt
.global _start

_start:
popl %ecx # argc

vnext:
popl %ecx # argv
test %ecx,%ecx # 空指针表明结束
jz exit
movl %ecx,%ebx
xorl %edx,%edx

strlen:
movb (%ebx),%al
inc %edx
inc %ebx
test %al,%al
jnz strlen
movb $10,-1(%ebx)
movl $4,%eax # 系统调用号(sys_write)
movl $1,%ebx # 文件描述符(stdout)
int $0x80
jmp vnext

exit: movl $1,%eax # 系统调用号(sys_exit)
xorl %ebx,%ebx # 退出代码
int $0x80
ret

GCC inline assembly

Although the program written in assembly runs fast, the development speed is very slow and the efficiency is low. If it is only optimized relative to the key code segment, perhaps a better way is to embed assembly instructions into the C language program, so as to make full use of the respective characteristics of high-level language and assembly language. Generally speaking, embedding assembly statements in C code is much more complicated than "pure" assembly language code, because it is necessary to solve the problems of how to allocate registers and how to combine with variables in C code.

GCC provides very good inline assembly support, the most basic format is: __asm__("asm statements"); . For example: __asm__("nop");

If you need to execute multiple assembly statements at the same time, you should use \\n\\t to separate each statement, for example:

__asm__("pushl %%eax \\n\\t"
"movl $0,%%eax \\n\\t"
"popl %eax")

Usually the assembly statements embedded in C code have nothing to do with other parts, so the complete inline assembly format is more often used: __asm__("asm statements" : outputs : inputs : registers-modified)

inserted into C code is compiled statements : separated four parts , wherein first part is a compilation of the code itself, commonly referred to as the instruction unit , in a format used in assembly language is substantially the same. The instruction part is necessary, while other parts can be omitted according to the actual situation.

When embedding assembly statements into C code, how to combine operands with variables in C code is a big problem. GCC uses the following method to solve this problem: the programmer provides specific instructions, and the use of registers only needs to give the template and constraint conditions. How to combine registers and variables is completely determined by GCC and GAS. Responsible

In the instruction part of the GCC inline assembly statement, % (such as: %0 , %1 template operands that need to use registers. Several sample operands are used in the instruction section, which indicates that there are several variables that need to be combined with registers, so that GCC and GAS will compile and assemble properly according to the constraints given later. Since the template operand also uses % as the prefix, when it comes to specific registers, two % should be added in front of the register name to avoid confusion

Immediately rear portion of the command output unit , is how the predetermined conditions for the output variable number model operating in conjunction with each condition is called a "constraint" may include a plurality of constraints, if necessary, from each other separated by commas Separate. Each output constraint starts with = , followed by a word describing the type of operand, and finally a constraint on how to combine with the variable. All registers or operands that are combined with the operands described in the output section will not retain the content before the execution after the embedded assembly code is executed. This is the basis used by GCC when scheduling registers

output part of 161348e2abbfc0 is followed by the input part . The format of the input constraint is similar to that of the output constraint, but without the = number. If an input constraint requires the use of a register, GCC will allocate a register for it during preprocessing, and insert the necessary instructions to load the operand into the register. The register or operand itself combined with the operand described in the input section will not retain the contents before the execution after the instruction is embedded in the assembly code.

Sometimes when performing certain operations, in addition to the registers for data input and output, multiple registers are also used to store the results of intermediate calculations, which will inevitably destroy the contents of the original registers. In the last part of the GCC inline assembly format, you can describe the registers that produce side effects, so that GCC can take corresponding measures

Here is a simple example of inline assembly:

#include <stdio.h>

int main()
{
int a = 10, b = 0;
__asm__ __volatile__("movl %1, %%eax;\n\r"
"movl %%eax,%0"
:"=r"(b)
:"r"(a)
:"%eax");
printf("result: %d, %d\n", a, b);
}

The above program is to assign variable a to variable b. There are a few points that need to be explained:

  • b is the output operand, %0 , and a is the input operand, referenced %1
  • Both the input operand and the output operand are r , which means that the two variables are stored in the register. The difference between input constraints and output constraints is that the output constraints have one more constraint modifier =
  • When using the register eax in an inline assembly statement, two % should be added in front of the register name. Inline assembly uses %0 , %1 etc. to identify variables. Any paper bag with an % is regarded as an operand, not a register
  • The last part of the inline assembly statement high-speed GCC will change the value in the register eax, GCC should not use this register to store any other values during processing
  • Since b is designated as the output operand, when the inline assembly statement is executed, its saved value will be updated

In inline assembly, the operands used are numbered from the first constraint in the output section, starting from 0, and each constraint is counted once. The instruction section needs to refer to these operands, just add % before the sequence number as a prefix That's it. It should be noted that the instruction part of the inline assembly statement always uses an operand as a 32-bit longword when referring to it. However, in actual situations, words or bytes may be required, so specify the correct qualifier in the constraint :

  • m , v , o : memory unit
  • r : any register
  • q : one of the registers eax, ebx, ecx, edx
  • i , h : direct operand
  • E , F : floating point numbers
  • g : Any
  • a , b , c , d : respectively represent the registers eax, ebx, ecx, edx
  • S , D : registers esi, edi
  • I : constant (0~31)

See Also

阅读 2.6k

我的前端之路
我的前端之路

如果觉得我的文章对大家有用的话, 可以去我的github start一下[链接]

1.2k 声望
24 粉丝
0 条评论

如果觉得我的文章对大家有用的话, 可以去我的github start一下[链接]

1.2k 声望
24 粉丝
文章目录
宣传栏