對ARM進行裸機開發時,彙編是必不可少的,因為C語言無法直接操作CPU的内置寄存器,也就無法完成很多硬體初始化的功能,如記憶體控制器的初始化。
市面上大多數講解ARM彙編開發的書籍都把ADS作為開發環境,是以使用的彙編語言也就是ARMASM,開發平台也限制到了Windows。然而在嵌入式領域,Linux作為開發環境更加普及,那麼如何在Linux下進行ARM的彙程式設計式設計呢?
其實,Linux平台上早就有了支援ARM的彙編器,那就是著名的binutils軟體包,它包含了彙編、連結、二進制操作等所有的ARM彙編環境。
1 binutils簡介
binutils是支援Unix類系統彙編與連結開源軟體包,提供彙編、連結等二進制檔案操作工具。其官方首頁是: http://www.gnu.org/software/binutils。著名的GCC編譯器本身并不提供彙編和連結功能,而是依賴于這個binutils才能完成進階語言的編譯、彙編、連結過程。它提供的工具包括:
* GNU連結器ld
* GNU彙編器as
* objcopy 轉換二進制格式
* ar 對象檔案打包管理
* c++filt C++符号名字反mangling
* nm 列印二進制檔案裡的符号資訊
* readelf 讀取分析elf格式檔案資訊
* ranlib 産生archive包的索引
* size 列出二進制檔案的大小
* objdump 列印二進制檔案資訊
* strip 去除二進制檔案中的符号資訊
* strings 列印二進制檔案中的可列印符号
* dlltool 為建構和使用DLL建立檔案
* gprof 顯示配置資訊
* addr2line 把記憶體位址對應到源檔案中的行号(用于調試)
* nlmconv 轉為對象代碼為NLM
* gold 隻用于ELF格式的新生代連接配接器(尚處于測試階段)
對于ARM彙程式設計式開發,as,ld,objcopy是最基本的工具。
2 binutils安裝
對于ARM彙編來說,最常見的就是開發平台是基于X86的LinuxPC機器,而運作平台則是基于ARM的嵌入式硬體平台。這就需要一個能生成ARM機器碼的交叉彙編器和連接配接器。binutils自然能夠滿足這個小小的要求,隻是需要進行編譯安裝就可以了。
2.1 準備binutils源碼
去其官網下載下傳最新的源碼包,截至目前(2016年12月12日)最新的版本為2.27,下載下傳得到的檔案名為: binutils-2.27.tar.bz2。
解壓
tar -jxvf binutils-.tar.bz2
得到binutils-2.27檔案夾。
不建議直接在binutils-2.27這個源檔案目錄下直接配置編譯,以免造成混亂。為此,建立一個空目錄build-binutils,用于編譯。
2.2 配置,編譯,安裝
進入build-binutils目錄,執行如下配置指令:
上面指定了binutils的安裝目錄與目标平台,運作完成會在目前目錄下生成Makefile。執行make,make install完成安裝
make
make install
與GCC不同,binutils的配置、編譯、安裝非常簡單,很少會出錯。安裝完成後會産生如下目錄結構:
ARM
├── arm-linux-gnueabihf
│ ├── bin
│ │ ├── ar
│ │ ├── as
│ │ ├── ld
│ │ ├── ld.bfd
│ │ ├── nm
│ │ ├── objcopy
│ │ ├── objdump
│ │ ├── ranlib
│ │ ├── readelf
│ │ └── strip
│ └── lib
│ └── ldscripts
│
├── bin
│ ├── arm-linux-gnueabihf-addr2line
│ ├── arm-linux-gnueabihf-ar
│ ├── arm-linux-gnueabihf-as
│ ├── arm-linux-gnueabihf-c++filt
│ ├── arm-linux-gnueabihf-elfedit
│ ├── arm-linux-gnueabihf-gprof
│ ├── arm-linux-gnueabihf-ld
│ ├── arm-linux-gnueabihf-ld.bfd
│ ├── arm-linux-gnueabihf-nm
│ ├── arm-linux-gnueabihf-objcopy
│ ├── arm-linux-gnueabihf-objdump
│ ├── arm-linux-gnueabihf-ranlib
│ ├── arm-linux-gnueabihf-readelf
│ ├── arm-linux-gnueabihf-size
│ ├── arm-linux-gnueabihf-strings
│ └── arm-linux-gnueabihf-strip
└── share
└── man
└── man1
其中ARM/bin和ARM/arm-linux-gnueabihf/bin目錄下存放的就是as,ld等二進制工具。實際上這兩個目錄裡的檔案是完全一樣的,隻是名字不同而已。
[smstong@centos192 ~]$ ls ARM/arm-linux-gnueabihf/bin -i
ar ld nm objdump readelf
as ld.bfd objcopy ranlib strip
[smstong@centos192 ~]$ ls ARM/bin/ -i
arm-linux-gnueabihf-addr2line arm-linux-gnueabihf-nm
arm-linux-gnueabihf-ar arm-linux-gnueabihf-objcopy
arm-linux-gnueabihf-as arm-linux-gnueabihf-objdump
arm-linux-gnueabihf-c++filt arm-linux-gnueabihf-ranlib
arm-linux-gnueabihf-elfedit arm-linux-gnueabihf-readelf
arm-linux-gnueabihf-gprof arm-linux-gnueabihf-size
arm-linux-gnueabihf-ld arm-linux-gnueabihf-strings
arm-linux-gnueabihf-ld.bfd arm-linux-gnueabihf-strip
可見,ARM/arm-linux-gnueabihf/bin/as 和 ARM/bin/arm-linux-gnueabihf-as共享同一個索引節點号,是彼此的硬連結。為了與開發主機上的本地binutils相差別,建議使用長檔案名的那個硬連結。為此,把它們加入可執行路徑:
這樣我們就可以直接使用arm-linux-gnueabihf-as指令了。
3 目标機器是裸機下的彙程式設計式開發
下面直接給出一個具體的例子。
3.1 實驗環境
本次實驗的運作平台為TQ2440開發闆。從Norflash啟動硬體,并且Norflash中已經預裝了u-boot,且u-boot支援通過tftp協定下載下傳程式到指定記憶體位址執行。
開發主機安裝運作了tftp伺服器,檔案根目錄為/var/lib/tftpboot,編譯生成的二進制檔案led.bin被複制到此目錄下。
這就是說,測試程式執行前,u-boot已經完成了硬體平台的基本初始化:SDRAM可用,堆棧環境已經設定,MMU未啟用,是以虛拟位址和實體位址完全相同。
這樣我們的測試程式就可以做的非常簡單。
本次實驗的結果是熄滅底闆上的LED1。因為u-boot啟動後,LED1被自動點亮,我們的測試程式用來熄滅它。
3.2 源碼
源檔案 led.s。
我們都知道,GNU的X86彙編指令與Intel文檔提供的标準格式相差很大,而這種情況在ARM彙編中不再存在,
GNU ARM AS支援ARM公司提供的标準彙編指令格式,而且擴充了一些特有的僞指令。
.equ GPBCON,
.equ GPBDAT,
.equ GPBUP,
.equ UBOOT,
.section .text
.global _start
_start:
ldr r0, =GPBCON
ldr r1, [r0]
bic r1, r1, #0xC00
orr r1, r1, #0x400
str r1, [r0]
ldr r0, =GPBUP
ldr r1, [r0]
bic r1, r1, #0x20
str r1, [r0]
ldr r0, =GPBDAT
ldr r1, [r0]
orr r1, r1, #0x20
str r1, [r0]
b UBOOT /* 跳轉回Norflash,重新進入u-boot */
.end
連結腳檔案 led.lds:
ENTRY(_start)
SECTIONS {
. = ;
.text : { /* text and : must be seperated by space */
*(.text)
*(.rodata)
}
.data ALIGN(): {
*(.data)
}
.bss ALIGN(): {
*(.bss)
}
}
Makefile檔案:
AS = arm-linux-gnueabihf-as
LD = arm-linux-gnueabihf-ld
OBJCPY = arm-linux-gnueabihf-objcopy
all: led.bin
sudo cp led.bin /var/lib/tftpboot/
led.bin: led
$(OBJCPY) -O binary $< [email protected]
led: led.o
$(LD) --script=led.lds -o [email protected] $<
led.o: led.s
$(AS) -o [email protected] $<
.PHONY: clean
clean:
rm *.o led led.bin
3.3 編譯連結說明
- 連結時需要指定代碼段的起始記憶體位址為0x30000000,以滿足TQ2440開發闆的記憶體布局,這通過連結腳本led.lds來完成。
- 連結器生産的可執行程式led為elf格式,這是Linux作業系統下的标準可執行格式,需要作業系統提供的加載器才能加載執行,在開發闆裸機上不能直接運作,這就需要通過objcopy工具把elf格式轉化為單純的二進制格式led.bin。
整個轉換彙編連結過程:
led.s---(彙編器as)-->led.o---(連接配接器ld)-->led---(objcopy工具)--->led.bin
3.4 下載下傳到開發闆執行
##### Boot for Nor Flash Main Menu #####
##### EmbedSky USB download mode #####
[] Download u-boot or other bootloader to Nand Flash
[] Download Eboot (eboot.nb0) to Nand Flash
[] Download Linux Kernel (zImage.bin) to Nand Flash
[] Download WinCE NK.bin to Nand Flash
[] Download CRAMFS image to Nand Flash
[] Download YAFFS image (root.bin) to Nand Flash
[] Download Program (uCOS-II or TQ2440_Test) to SDRAM and Run it
[] Boot the system
[] Format the Nand Flash
[] Set the boot parameters
[a] Download User Program (eg: uCOS-II or TQ2440_Test)
[b] Download LOGO Picture (.bin) to Nand Flash
[l] Set LCD Parameters
[n] Enter TFTP download mode menu
[o] Download u-boot to Nor Flash
[r] Reboot u-boot
[t] Test Linux Image (zImage)
[q] quit from menu
Enter your selection: n
##### Boot for Nor Flash Main Menu #####
##### EmbedSky TFTP download mode #####
[] Download u-boot.bin to Nand Flash
[] Download Eboot (eboot.nb0) to Nand Flash
[] Download Linux Kernel (zImage.bin) to Nand Flash
[] Download WinCE NK.bin to Nand Flash
[] Set TFTP parameters(PC IP,TQ2440 IP,Mask IP...)
[] Download YAFFS image (root.bin) to Nand Flash
[] Download Program (uCOS-II or TQ2440_Test) to SDRAM and Run it
[] Boot the system
[] Format the Nand Flash
[] Set the boot parameters
[a] Download User Program (eg: uCOS-II or TQ2440_Test)
[b] Download LOGO Picture (.bin) to Nand Flash
[l] Set LCD Parameters
[o] Download u-boot to Nor Flash
[p] Test network (TQ2440 Ping PC's IP)
[r] Reboot u-boot
[t] Test Linux Image (zImage)
[q] Return main Menu
Enter your selection:
Enter downloads to SDRAM address:
Enter program name:
led.bin
tftp led.bin
dm9000 i/o: , id:
MAC: a:b:c:d:e:f
TFTP from server ; our IP address is
Filename 'led.bin'.
Load address:
Loading: #
done
Bytes transferred = ( hex)
## Starting application at ...
可以看到開發闆上的LED1燈已經被熄滅了。
4 目标機器是Linux系統下的彙程式設計式開發
彙程式設計式當然也适合在Linux系統下運作,而且有了OS的支援,彙程式設計式可以通過系統調用的方式非常爽的使用OS提供的API。下面給出一個hello world的例子。
4.1 源碼
源碼非常簡單hello.s:
.section .data
msg:
.asciz "hello,GNU ARM ASM\n"
.section .text
.global _start
_start:
mov r0,#1 /* file fd */
ldr r1, =msg
mov r2,#18
swi #0x900004 /* sys_write(fd,msg,len) */
mov r0,#0
swi #0x900001 /* sys_exit(code) */
Makefile:
AS = arm-linux-gnueabihf-as
LD = arm-linux-gnueabihf-ld
all: hello
sudo cp hello /var/lib/tftpboot/
hello: hello.o
$(LD) -o [email protected] $<
hello.o: hello.s
$(AS) -o [email protected] $<
.PHONY: clean
clean:
rm *.o hello
4.2 編譯連結說明
預設情況下連結器會把_start作為入口,代碼段基位址預設為0x00010074。因為開發闆運作的是Linux系統,是以會把這個虛拟位址轉換為可用的實體位址。
通過swi軟中斷方式調用Linux核心API,很輕易就實作了列印字元串的功能。在OS下開發程式是多麼幸福的事情!
4.3 系統調用還是标準C庫?
彙程式設計式有兩種使用系統API的方式,一是上面例子中的直接使用swi陷入核心;二是調用标準C庫函數。個人建議還是第一種方法更好,因為第二種方法存在如下問題:
- 開發環境下,目标機器的标準C庫不一定存在,例如我們目前的環境,由于還沒有編譯安裝标準C庫;這樣交叉連結無法完成連結任務。
也許有人說,使用标準C庫會使得程式具有更強的可移植性。可是别忘了,我們開發的是彙程式設計式,彙程式設計式天生就不具有可移植性!!!