C指標原理(1)-AT&T彙編
彙編在LINUX系統下的意義遠遠大於WINDOWS系統,LINUX核心部分程式碼就是彙編編寫的。然後,絕大多數 Linux 程式設計師以前只接觸過DOS/Windows 下的組合語言,這些彙編程式碼都是 Intel 風格的。但在 Unix 和 Linux 系統中,更多采用的還是 AT&T 格式,兩者在語法格式上有著很大的不同,因此應對AT&T彙編應有一個基本的瞭解和熟悉。
我們在LINUX下用C編寫一段最簡單的helloworld程式,命令為hello.c
#include <stdio.h> int main() { printf("hello,world\n"); exit(0); }
然後,使用GCC編譯,同時使用-s引數生成中間彙編程式碼,看看AT&T彙編的真實面目
.section .data#初始化的變數 output: .ascii "hello,world\n" #要列印的字串,.data為初始化值的變數。output是標籤,指示字串開始的位置,ascii為資料型別 .section .bss#未初始化的變數,由0填充的緩衝區 .lcomm num,20 #lcomm為本地記憶體區域,即本地彙編外的不能進行訪問。.comm是通用記憶體區域。 .section .text#組合語言指令碼 .globl _start#啟動入口 _start: movl $4,%eax#呼叫的系統功能,4為write movl $output,%ecx#要列印的字串 movl $1,%ebx#檔案描述符,螢幕為1 movl $12,%edx#字串長度 int $0x80#顯示字串hello,world movl $0,%eax movl $num,%edi movl $65,1(%edi)#A 的ascii movl $66,2(%edi)#B 的ascii movl $67,3(%edi)#C 的ascii movl $68,4(%edi)#D 的ascii movl $10,5(%edi)#\n的ascii movl $4,%eax#呼叫的系統功能,4為write movl $num,%ecx#要列印的字串 movl $1,%ebx#檔案描述符,螢幕為1 movl $6,%edx#字串長度 int $0x80#顯示字串ABCD movl $1,%eax#1為退出 movl $0,%ebx#返回給shell的退出程式碼值 int $0x80#核心軟中斷,退出系統 gcc -S hello.c .file "hello.c" .section .rodata .LC0: .string "hello,world" .text .globl main .type main, @function main: pushl %ebp movl %esp, %ebp andl $-16, %esp subl $16, %esp movl $.LC0, (%esp) call puts movl $0, (%esp) call exit .size main, .-main .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3" .section .note.GNU-stack,"",@progbits
彙編器(assembler)的作用是將用匯編語言編寫的源程式轉換成二進位制形式的目的碼。Linux 平臺的標準彙編器是 GAS,它是 GCC 所依賴的後臺彙編工具,通常包含在 binutils 軟體包中。
AT&T彙編主要有以下特點:
1、在 AT&T 彙編格式中,暫存器名要加上 '%' 作為字首。
如:
把eax暫存器的內容複製到ebx中
movl %eax,%ebx
2、用 '$' 字首表示一個立即運算元。
如:將1複製到eax中
movl $1, %eax
3、目標運算元在源運算元的右邊
movl %eax,%ebx
eax是源運算元,ebx是目標運算元
4、在 AT&T 彙編格式中,運算元的字長由操作符的最後一個字母決定,字尾'b'、'w'、'l'分別表示運算元為位元組(byte,8 位元)、字(word,16 位元)和長字(long,32位元)
比如:
movl對32位進行操作,將eax暫存器32位的內容複製到ebx中
movl %eax, %ebx
movw對16位進行操作,將ax暫存器的內容複製到bx中
movw %ax, %bx
movb對8位進行操作,將al暫存器的內容複製到bl中
movb %al, %bl
我們再以入棧為例:
pushl %ecx # 32位ecx的內容入棧
pushw %cx # 16位ecx的內容入棧
pushl $180 # 80做為一個32位整數入棧
pushl data # data變數內容入棧,長度為32位
pushl $data # 這一個操作很特別,在變數前面加上$表示取變數的地址,這是將data變數的地址入棧
5、在 AT&T 彙編格式中,絕對轉移和呼叫指(jump/call)的運算元前要加上'*'作為字首
6、遠端轉移指令和遠端子呼叫指令的操作碼,在 AT&T 彙編格式中為 ljump和lcall
我們從生成的中間程式碼可以看出這幾個特點。
我們再來看一段用AT&T彙編編寫的helloworld程式。
.section .data#初始化的變數
output:
.ascii "hello,world\n"
#要列印的字串,.data為初始化值的變數。output是標籤,指示字串開始的位置,ascii為資料型別
.section .bss#未初始化的變數,由0填充的緩衝區
.lcomm num,20
#lcomm為本地記憶體區域,即本地彙編外的不能進行訪問。.comm是通用記憶體區域。
.section .text#組合語言指令碼
.globl _start#啟動入口
_start:
movl $4,%eax#呼叫的系統功能,4為write
movl $output,%ecx#要列印的字串
movl $1,%ebx#檔案描述符,螢幕為1
movl $12,%edx#字串長度
int $0x80#顯示字串hello,world
movl $0,%eax
movl $num,%edi
movl $65,1(%edi)#A 的ascii
movl $66,2(%edi)#B 的ascii
movl $67,3(%edi)#C 的ascii
movl $68,4(%edi)#D 的ascii
movl $10,5(%edi)#\n的ascii
movl $4,%eax#呼叫的系統功能,4為write
movl $num,%ecx#要列印的字串
movl $1,%ebx#檔案描述符,螢幕為1
movl $6,%edx#字串長度
int $0x80#顯示字串ABCD
movl $1,%eax#1為退出
movl $0,%ebx#返回給shell的退出程式碼值
int $0x80#核心軟中斷,退出系統
我們對上面這段彙編程式碼的結構和內容進行解說:
1、.section .data段存放著初始化的變數, .section .bss段存放著未初始化的變數
2、變數的定義採用以下格式:
變數名:
變數型別 變數值
上面程式碼中的output變數就是這麼定義的
output:
.ascii "hello,world\n"
下面例子定義了多個變數
.section .data
msg:
.ascii “This is a text”
x:
.double 109.45, 2.33, 19.16
y:
.int 89
z:
.int 21, 85, 27
.equ a 8
其中,msg為字元符,x為雙精度符點數,y和z為整數,a是一個特別的定義,它的是一個靜態變數的定義,使用.equ 變數名 變數值來實現
3、.section .bss段中變數訪問區域的定義規則為:
lcomm為本地記憶體區域,即本地彙編外的不能進行訪問,而.comm是通用記憶體區域
比如上面的定義
.lcomm num,20
num為本地記憶體區域。
4、section .text段為組合語言指令碼,使用.globl _start指示_start標記後的程式碼為程式啟動入口。
5、#表示註釋,上面程式碼的其它部分均有註釋,有彙編基礎的程式設計師應很容易理解
變數的型別有以下幾種:
.ascii 文字字串
.asciz 以NULL結束的文字字串
.byte 位元組值
.double 雙精度符點數
.float 單精度符點數
.int 32位整數
.long 32位整數
.octa 16位整數
.quad 8位整數
.short 16位整數
.single 單精度符點數
此外,AT&T彙編經常會涉及位元組順序反轉,比較載入,交換,壓入彈出所有暫存器等操作,以下例子涉及了這些操作,
每行程式碼都有詳細的註釋。
.bss段定義的資料元素為未初始化的變數,在執行時對其進行初始化。
可分為資料通用記憶體區域和本地通用記憶體區域
本地通用記憶體區域不能從本地彙編程式碼之外進行訪問。
.text段存放程式碼