gas學習 by Jian Lee

  • 利用 cpuid 取得 intel 集容cpu的廠商信息。
.section .data
output1:
    .ascii "當前CPU廠商ID是:"
output2:
    .ascii "xxxxxxxxxxxx\n"
.section .text
.global _start
_start:
    movl $0,%eax
    cpuid

    movl $output2,%edi
    movl %ebx,(%edi)
    movl %edx,4(%edi)
    movl %ecx,8(%edi)

    movl $4,%eax
    movl $1,%ebx
    movl $output1,%ecx
    movl $(output2-output1+14),%edx
    int $0x80

    movl $1,%eax
    movl $0,%ebx
    int $0x80

編譯和鏈接上面的源代碼並運行:

as -o cpuid.o cpuid.s # 彙編源代碼為目標文件
ld -o cpuid cpuid.o # 鏈接目標文件為linux可執行的文件
./cpuid # 執行

1.1 基本gas程序模板

gas程序分為幾個部分,通常有data段和text段。data段存放數據,其 內容會被放在最終的可執行程序中。text段是彙編指令。通常還有 bss段,這裡相當於緩存,臨時創建。

.section .data
    ...
.section .text
    ...

1.2 gas彙編程序偽指令

一般彙編程序本身會提供一些方便的"指令",但是這些"指令"不是 cpu指令,它們只是方便彙編程序開發者,解釋權歸彙編程序本身。

gas中的“偽指令”都是以 "." 開頭,如上面示例中的一些偽指令:

.section     定義一個段
.data        同.section一起定義數據段
.ascii       定義一個字符串
.text        同.section一起定義程序段
.global      定義全局標籤

###1.3 gas的標籤

在gas中的標籤等價於一個內存地址,相當於C語言中的指針。不過C語 言的指針還有數據類型屬性,這裡的數據類型需要自己指定了。

gas中的標籤是以冒號結尾的字符串。如示例中的:

output1:
output2:

既然gas中的標籤是內存地址,這是一個數值。那麼,我們可以在gas 程序中對標籤進行算術運算,且對於“立即數”能出現的地方,標籤也 都可以出現:

movl $(output2-output1+14),%

###1.4 gas的開始標籤

如果用 ld 手動鏈接程序,則需要在彙編程序中設置一個為 _start: 的開始標籤,相當於C程序中的main函數。如果用gcc編譯彙編程序, 那麼要將"_start:" 換成 "main:"

如果沒有指定 "_start" 標籤,可以在運新 ld 命令的時候指定開始標籤:

ld -e

###1.5 gas中的系統調用

Linux下的彙編程序可以使用系統調用,這樣可以避免很多複雜的工作。 在gas裡使用系統調用就是在相關寄存器存入系統調用號、調用函數的 參數,再執行 "int $0x80" 命令就可以了。

上例使用了兩個系統調用函數: write 和 exit

1.6 基本 AT&T 彙編風格

  • 立即數 表示立即數要用"$"符號
  • 寄存器 表示寄存器要用"%"符號
  • 內存尋址 內存地址要放在寄存器裡,才能被尋址,這是intel的cpu指令集規定的。

movl %ebx,(%edi) # 將ebx裡面的內容放到edi指向的內存地址單元處。 movl %edx,4(%edi) # 將edx裡面的內容放到比edi指向的內存地址高4個值的內存單元處。

2. 使用標準C庫函數

  • 使用標準C庫函數的 cpuid2.s 程序:
.section .data
output:
    .asciz "當前CPU廠商信息是:%s \n"
.section .bss
    .lcomm buffer,12

.section .text
.global _start
_start:
    movl $0,%eax
    cpuid

    movl $buffer,%edi
    movl %ebx,(%edi)
    movl %edx,4(%edi)
    movl %ecx,8(%edi)

    pushl $buffer
    pushl $output
    call printf

    addl $8,%esp
    pushl $0
    call exit

彙編、鏈接並運行:

as -o cpuid2.o cpuid2.s
ld -dynamic-linker /lib/ld-linux.so.2 -o cpuid2 -lc cpuid2.o

參數說明:

-lc 鏈接C庫/lib/libc.so;如果是-lx,默認鏈接/lib/libx.so。
-dynamic-linker /lib/ld-linux.so.2 使用/lib/ld-linux.so.2加載共享庫。

##. 定義數據元素

3.1 定義數據元素的命令

.ascii     文本字符串
.asciz     帶零結束符的文本字符串
.byte      字節值
.double    雙精度值
.float     單精度值
.int       32位整數
.long      同.int
.octa      16字節長度整數
.quad      8字節長整數
.short     16位整數
.single    同.float

有幾種數據段:

.section .data    定義通常的數據段,數據被包含在最終程序中
.section .rodata  同.data,但是這裡定義的數據的值不可修改
.section .bss     相當於緩衝

數據定義可以一個標籤一個命令一個數據的定義:

output:
    .asciz "這是一個.asciz定義的字符串"
value:
    .int  100

也可以,一個標籤一個命令定義一堆數據:

values:
    .int 15,20,25,30,35,40,45,50,55,60

無論是哪種形式定義的,數據在內存中都是一個挨著一個的存放的。 這樣我們可以用索引來引用它們。

###3.2 賦值命令

gas中也可以用一個符號代表一個值,但是符號只不可修改:

.equ factor,3
.equ LINUX_SYS_CALL,0x80

3.3 bss段

這個段無須聲明特定的類型,只要聲明大小:

.comm  聲明未初始化數據的通用內存區域
.lcomm 聲明未初始化數據的"本地"通用內存區域

例如,聲明一個1000字節的緩衝區,通過buffer引用這個區域的基址:

.section .bss
  .lcomm buffer,1000

3.4 .fill 命令

這個命令讓彙編器自動創建一段內存區域,並用0填充:

.section .data
buffer:
    .fill 1000

4. MOV命令

gas中把mov命令加了不同後綴,每個後綴表示操作不同的數據大小:

movl  32位
movw  16
movb  8

下面一個例子把一個值移到ecx寄存器,然後調用linux系統的exit正 常退出。用gdb可以看見寄存器的變化。

# 程序 movetest1.s
.section .data
value:
    .int 1
.section .text
.global _start
_start:
    nop
    movl value,%ecx
    movl $1,%eax
    movl $0,%ebx
    int $0x80

用-gtabs參數彙編程序並鏈接:

as -gtabs -o movetest1.o movetest1.s
ld -o movetest1 movetest1.o

使用gdb調試程序:

root@jianlee:~/lab/asm# gdb -q movetest1
(gdb) break *_start+1
Breakpoint 1 at 0x8048075: file movetest1.s, line 8.
(gdb) run
Starting program: /root/lab/asm/movetest1

Breakpoint 1, _start () at movetest1.s:8
8	        movl value,%ecx
Current language:  auto; currently asm
(gdb) print/x %ecx
A syntax error in expression, near `%ecx'.
(gdb) print/x $ecx
$1 = 0x0
(gdb) next
_start () at movetest1.s:9
9	        movl $1,%eax
(gdb) print/x $ecx
$2 = 0x1
(gdb) s
_start () at movetest1.s:10
10	        movl $0,%ebx
(gdb) cont
Continuing.

Program exited normally.
(gdb)

把寄存器的值傳到內存裡:

# movetest2.s 把寄存器的值傳到內存中
.section .data
value:
    .int 1
.section .text
.global _start
_start:
    nop
    movl $100,%eax
    movl %eax,value

    movl $1,%eax
    movl $0,%ebx
    int $0x80

調試程序,查看程序執行過程:

root@jianlee:~/lab/asm# as -gtabs -o movetest2.o movetest2.s
movetest2.s: Assembler messages:
movetest2.s:0: Warning: end of file not at end of a line; newline inserted
root@jianlee:~/lab/asm# ld -o movetest2 movetest2.o
root@jianlee:~/lab/asm# gdb -q movetest2
(gdb) break *_start+1
Breakpoint 1 at 0x8048075: file movetest2.s, line 9.
(gdb) run
Starting program: /root/lab/asm/movetest2

Breakpoint 1, _start () at movetest2.s:9
9	        movl $100,%eax
Current language:  auto; currently asm
(gdb) print/x $eax
$1 = 0x0
(gdb) x/d &value
0x804908c <value>:	1
(gdb) s
_start () at movetest2.s:10
10	        movl %eax,value
(gdb) print/x $eax
$2 = 0x64
(gdb) x/d &value
0x804908c <value>:	1
(gdb) s
_start () at movetest2.s:12
12	        movl $1,%eax
(gdb) x/d &value
0x804908c <value>:	100
(gdb) cont
Continuing.

Program exited normally.
(gdb)

书籍推荐