1. 程式人生 > >arm彙編align偽指令

arm彙編align偽指令

出處:netwalker.blog.chinaunix.net
=========================================== 一個值得討論的偽指令是.align,它可能在很多時候不被人注意,但是不恰當的使用將導致程式無法執行,這種可能性在ARM系統上幾乎是百分之百的發生。

.align的作用在於對指令或者資料的存放地址進行對齊,有些CPU架構要求固定的指令長度並且存放地址相對於2的冪指數圓整,否則程式無法正常 執行,比如ARM;有些系統卻不需要,如果不遵循地址的圓整規則,程式依然可以正確執行,只是降低了一些執行效率,比如i386。.align的作用範圍 只限於緊跟它的那條指令或者資料,而接下來的指令或者資料的地址由上一條指令的地址和其長度決定。這裡給出一個很好的用來測試.align作用的例子,首 先在i386上進行測試。

  1. .section .text #定義程式碼段
  2. data:
  3. .byte 0x11
  4. .align 4
  5. .globl _start
  6. _start:
  7. movl data, %ebx
  8. movl $1, %eax
  9. int $0x80

這個程式沒有實際的應用意義。為了防止編譯出的目標檔案中不同的段均從0開始而看不到.align的效果,這裡只定義一個程式碼段,.byte資料將 被編譯進程式碼段,_start中的第一條指令將緊跟在0x11資料之後,我們使用 as -o test.o test.S && objdump -D test.o來檢視反彙編的結果:

  1. Disassembly of section .text:
  2. 00000000 :
  3. 0: 11 8d 76 00 bb 00 adc %ecx,0xbb0076(%ebp)
  4. 00000004 <_start>:
  5. 4: bb 00 00 00 00 mov $0x0,%ebx
  6. 9: a1 00 00 00 00 mov 0x0,%eax
  7. e: cd 80 int $0x80

objdump嘗試將程式碼段中的所有二進位制資料當作指令解析,所以不要關心非程式碼段反彙編後的指令adc %ecx,0xbb0076(%ebp)。我們需要關心的是mov $0x0,%ebx所在的地址4,顯然它和.align指定的4可以除盡,也即相對齊於4的倍數的地址。為了作一比較,移除.align 4,得到以下的反彙編結果,顯然此時的第一條mov指令的對齊地址是1。

  1. Disassembly of section .text:
  2. 00000000 :
  3. 0: 11 bb 00 00 00 00 adc %edi,0x0(%ebx)
  4. 00000001 <_start>:
  5. 1: bb 00 00 00 00 mov $0x0,%ebx
  6. 6: a1 00 00 00 00 mov 0x0,%eax
  7. b: cd 80 int $0x80

對於i386編譯器來說.align的引數4直接指明瞭對齊地址的圓整目標,彙編器總是要找到下一個可以整除該引數的地址作為.align後的那條 指令的存放地址。.align引數的取值必須是2的冪指數,2^0到2^31都是合法的,而其它的值將會遭遇編譯錯誤的提示:

  1. # as -o test.o test.S
  2. test.S: Assembler messages:
  3. test.S:4: Error: alignment not a power of 2

對於ARM編譯器來說.align的用法和i386有相當大的差異,這也就是.align被混亂使用的根本原因。將上面的彙編程式改成ARM平臺的組合語言後的結果如下:

  1. .section .test
  2. data:
  3. .byte 0x11
  4. .align 4
  5. .global _start
  6. _start:
  7.  mov r0, #data
  8.  mov r7, #1
  9.  svc 0x00000000

與它對應的反編譯結果如下:

  1. # arm-linux-as asm.S -o asm.o && arm-linux-objdump -D asm.o
  2. ......
  3. Disassembly of section .test:
  4. 00000000 <data>:
  5.    0:    00000011     .word    0x00000011
  6.     ...
  7. 00000010 <_start>:
  8.   10:    e3a00000     mov    r0, #0    ; 0x0
  9.   14:    e3a07001     mov    r7, #1    ; 0x1
  10.   18:    ef000000     svc    0x00000000
  11.   1c:    00000000     andeq    r0, r0, r0
  12. Disassembly of section .ARM.attributes:
  13. ......

出乎意料的是mov r0,4 #0指令的地址是0x10,也即16,不是4。ARM彙編器並不直接使用.align提供的引數作為對齊目標,而是使用2^n的值,比如這裡的引數為4, 那麼圓整物件為2^4 = 16。這也就是為什麼在ARM平臺的Uboot或者Linux核心彙編中會出現.align 5的根本原因。.align此時的取值範圍為0-15,當取值為0,2或者不提供引數時均圓整於4。如果嘗試使用大於15的值,將會得到編譯器的如下抱 怨:

  1. # arm-linux-as asm.S -o asm.o
  2. asm.S: Assembler messages:
  3. asm.S:4: Error: alignment too large: 15 assumed

另一點需要注意的是:ARM嘗試使用0來填充.byte佔用的一個字(ARM平臺為4bytes)中剩餘的位元位,但是其餘的字將會用nop指令填 充,這一點與i386也是不同的。以上的討論關注於目標檔案.o,而非最終的可執行檔案,一個對於連結器的猜疑是它會不會改變我們的圓整目標,慶幸的是它 不會,它會找到當前.o檔案中的最大圓整值,並且以它的倍數偏移。由ARM支援非對齊地址的訪問,所以指定指令的對齊即可保證程式的正確執行,但要保證程 序的訪問效率,對資料的對齊也是尤為重要。

如果分析過Uboot,可能會遇到類似.balignl的操作,對於是用如此生僻指令的開發習慣,這並不是好的做法,除非是迫不得已,否則它可以用更通用的指令代替。.balignl指令是.balign指令的變體,一個完整的指令是格式如下:

  1. .balign[wl] align, fill_value, max_padding

.balign的所有引數均是可選的,align引數類細雨.align偽指令引數,但是這裡它直接作為對齊引數,類似於i386平臺 的.align引數。fill_value用來填充空白位元組的值,沒有提供該引數,預設將是用nop指令填充。max_padding指定了最多可填充的 位元組數,如果實際需要填充的位元組數大於該值那麼整個.balign偽指令將失效。.balign使用位元組來填充,比如.balign 4,0x10;.balignw使用雙位元組來填充,比如.balignw 4,0x1122;.balignl使用四位元組來填充,比如.balignl 4,0x11223344。注意所有需要填充的位元組數必須是[wl]引數指定的整倍數,否則將遭遇彙編器的抱怨:

  1. .byte 0x11
  2. .balignl 16,0x33445566
  3. .global _start
  4. _start:
  5.  mov r7, #1
  6.  swi 0x0000000
  7. # arm-linux-as arm.S -o arm.o
  8. arm.S: Assembler messages:
  9. arm.S:3: Error: alignment padding (15 bytes) not a multiple of 4
  10. arm.S:3: Error: alignment padding (15 bytes) not a multiple of 4

為了保證整倍數關係,如果刪除.byte 0x11或者改成如下程式碼,抱怨將消失:

  1. .byte 0x11
  2. .ascii "123"
  3. .balignl 16,0x33445566
  4. .global _start
  5. _start:
  6.  mov r7, #1
  7.  swi 0x0000000
  8. # arm-linux-as arm.S -o arm.o
注意 .ascii和.asciz的區別是,.asciz會在字串後自動新增結束符\0.