汇编语言使用C库函数和Linux动态链接
使用printf
代碼
#cpuid2.s -- Using C labrary calls
.section .data
output: .asciz "The processor Vender is '%s'\n"
.section .bss .lcomm buffer, 12
.section .text
.globl _start
_start: movl $0, %eax cpuid movl $buffer, %edi movl %ebx, (%edi) //含義同cpuid.s,向%edi所指向的buffer內(nèi)存 movl %edx, 4(%edi) //位置寫入3個寄存器的內(nèi)容,這3個寄存器存儲了 movl %ecx, 8(%edi) //CPU廠商信息的字符串(12字節(jié)) pushl $buffer //將buffer和output入棧,為printf提供參數(shù) pushl $output call printf //調(diào)用C的printf函數(shù) addl $8, %esp //將堆棧指針回滾8個字節(jié),達(dá)到清除printf參數(shù)的目的 pushl $0 call exit
?
.asciz是在定義字符串的時侯在字符串結(jié)尾加上空字符(即C語言的\0),這樣做的目的是為了讓printf能讀懂字符串。
.lcomm是在本地內(nèi)存區(qū)域中聲明固定長度的未初始化數(shù)據(jù),這里初始化了12個字節(jié)的空間。
程序里buffer和output內(nèi)存位置的內(nèi)容是要向printf傳遞的參數(shù)值:
一個是"The processor Vender is '%s'\n"字符串;
另外一個是由cpuid返回結(jié)果(在ebx,edx,ecx三個寄存器中)填充的buffer。
需要通過堆棧來傳遞參數(shù),所以在程序中使用
pushl $buffer
pushl $output將參數(shù)入棧,printf獲取參數(shù)是自右向左,即先buffer后output,所以要把buffer后入棧。
參數(shù)入棧之后,用call指令調(diào)用printf。
exit的情況同上,使用了一個參數(shù)--常數(shù)0。
匯編
#as -o cpuid2.o cpuid2.s
采用了動態(tài)連接的方式,所以C函數(shù)沒有包含在可執(zhí)行程序中,需要由另外的程序在運(yùn)行時加載,ld不知道這個程序在哪里,所以我們還得手動指定
動態(tài)鏈接
#ld -dynamic-linker /lib/ld-linux.so.2 -lc -o cpuid2 cpuid2.o
#./cpuid2
輸出
The processor Vendor ID is 'GenuineIntel'
GDB調(diào)試
# gdb cpuid2
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-56.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /usr/zhms/AS/chap04/cpuid2...done.
(gdb) break _start
Breakpoint 1 at 0x80481b8: file cpuid2.s, line 10.
(gdb) r
Starting program: /usr/zhms/AS/chap04/cpuid2 Breakpoint 1, _start () at cpuid2.s:10
10 movl $0, %eax
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.80.el6_3.6.i686
(gdb) i r
eax 0x0 0
ecx 0x0 0
edx 0x758470 7701616
ebx 0x767fc4 7765956
esp 0xbffff6c0 0xbffff6c0
ebp 0x0 0x0
esi 0xbffff6cc -1073744180
edi 0x80481b8 134513080
eip 0x80481b8 0x80481b8 <_start>
eflags 0x286 [ PF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) s
11 cpuid
(gdb) i r
eax 0x0 0
ecx 0x0 0
edx 0x758470 7701616
ebx 0x767fc4 7765956
esp 0xbffff6c0 0xbffff6c0
ebp 0x0 0x0
esi 0xbffff6cc -1073744180
edi 0x80481b8 134513080
eip 0x80481bd 0x80481bd <_start+5>
eflags 0x286 [ PF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) s
13 movl %ebx, (%edi)
(gdb) i r
eax 0x5 5
ecx 0x6c65746e 1818588270
edx 0x49656e69 1231384169
ebx 0x756e6547 1970169159
esp 0xbffff6c0 0xbffff6c0
ebp 0x0 0x0
esi 0xbffff6cc -1073744180
edi 0x80492c0 134517440
eip 0x80481c4 0x80481c4 <_start+12>
eflags 0x286 [ PF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) s
14 movl %edx, 4(%edi)
(gdb) i r
eax 0x5 5
ecx 0x6c65746e 1818588270
edx 0x49656e69 1231384169
ebx 0x756e6547 1970169159
esp 0xbffff6c0 0xbffff6c0
ebp 0x0 0x0
esi 0xbffff6cc -1073744180
edi 0x80492c0 134517440
eip 0x80481c6 0x80481c6 <_start+14>
eflags 0x286 [ PF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) s
15 movl %ecx, 8(%edi)
(gdb) i r
eax 0x5 5
ecx 0x6c65746e 1818588270
edx 0x49656e69 1231384169
ebx 0x756e6547 1970169159
esp 0xbffff6c0 0xbffff6c0
ebp 0x0 0x0
esi 0xbffff6cc -1073744180
edi 0x80492c0 134517440
eip 0x80481c9 0x80481c9 <_start+17>
eflags 0x286 [ PF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) s
16 pushl $buffer
(gdb) i r
eax 0x5 5
ecx 0x6c65746e 1818588270
edx 0x49656e69 1231384169
ebx 0x756e6547 1970169159
esp 0xbffff6c0 0xbffff6c0
ebp 0x0 0x0
esi 0xbffff6cc -1073744180
edi 0x80492c0 134517440
eip 0x80481cc 0x80481cc <_start+20>
eflags 0x286 [ PF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) s
17 pushl $output
(gdb) i r
eax 0x5 5
ecx 0x6c65746e 1818588270
edx 0x49656e69 1231384169
ebx 0x756e6547 1970169159
esp 0xbffff6bc 0xbffff6bc
ebp 0x0 0x0
esi 0xbffff6cc -1073744180
edi 0x80492c0 134517440
eip 0x80481d1 0x80481d1 <_start+25>
eflags 0x286 [ PF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) s
18 call printf
(gdb) i r
eax 0x5 5
ecx 0x6c65746e 1818588270
edx 0x49656e69 1231384169
ebx 0x756e6547 1970169159
esp 0xbffff6b8 0xbffff6b8
ebp 0x0 0x0
esi 0xbffff6cc -1073744180
edi 0x80492c0 134517440
eip 0x80481d6 0x80481d6 <_start+30>
eflags 0x286 [ PF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) s
0x007b63e0 in printf () from /lib/libc.so.6
(gdb) i r
eax 0x5 5
ecx 0x6c65746e 1818588270
edx 0x49656e69 1231384169
ebx 0x756e6547 1970169159
esp 0xbffff6b4 0xbffff6b4
ebp 0x0 0x0
esi 0xbffff6cc -1073744180
edi 0x80492c0 134517440
eip 0x7b63e0 0x7b63e0 <printf>
eflags 0x246 [ PF ZF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) s
Single stepping until exit from function printf,
which has no line number information.
The processor Vendor ID is 'GenuineIntel'
_start () at cpuid2.s:19
19 addl $8, %esp
(gdb) i r
eax 0x2a 42
ecx 0xbffff6a0 -1073744224
edx 0x8fe364 9429860
ebx 0x756e6547 1970169159
esp 0xbffff6b8 0xbffff6b8
ebp 0x0 0x0
esi 0xbffff6cc -1073744180
edi 0x80492c0 134517440
eip 0x80481db 0x80481db <_start+35>
eflags 0x286 [ PF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) s
20 pushl $0
(gdb) i r
eax 0x2a 42
ecx 0xbffff6a0 -1073744224
edx 0x8fe364 9429860
ebx 0x756e6547 1970169159
esp 0xbffff6c0 0xbffff6c0
ebp 0x0 0x0
esi 0xbffff6cc -1073744180
edi 0x80492c0 134517440
eip 0x80481de 0x80481de <_start+38>
eflags 0x296 [ PF AF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) s
21 call exit
(gdb) i r
eax 0x2a 42
ecx 0xbffff6a0 -1073744224
edx 0x8fe364 9429860
ebx 0x756e6547 1970169159
esp 0xbffff6bc 0xbffff6bc
ebp 0x0 0x0
esi 0xbffff6cc -1073744180
edi 0x80492c0 134517440
eip 0x80481e0 0x80481e0 <_start+40>
eflags 0x296 [ PF AF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) s
0x00799060 in exit () from /lib/libc.so.6
(gdb) i r
eax 0x2a 42
ecx 0xbffff6a0 -1073744224
edx 0x8fe364 9429860
ebx 0x756e6547 1970169159
esp 0xbffff6b8 0xbffff6b8
ebp 0x0 0x0
esi 0xbffff6cc -1073744180
edi 0x80492c0 134517440
eip 0x799060 0x799060 <exit>
eflags 0x246 [ PF ZF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) s
Single stepping until exit from function exit,
which has no line number information.Program exited normally.
(gdb) i r
The program has no registers now.
(gdb)
?
ld-linux.so查找共享庫的順序
這里引出一個問題,怎么知道ld-linux.so.2是什么以及定位
Glibc安裝的庫中有一個為ld-linux.so.X,其中X為一個數(shù)字,在不同的平臺上名字也會不同。可以用ldd查看:
#ldd /bin/cat
最后一個沒有=>的就是。其中第一個不是實(shí)際的庫文件,你是找不到的,它是一個虛擬庫文件用于和kernel交互。
ld-linux.so是專門負(fù)責(zé)尋找?guī)煳募膸臁R詂at為例,cat首先告訴ld-linux.so它需要libc.so.6這個庫文件,ld-linux.so將按一定順序找到libc.so.6庫再給cat調(diào)用。
那ld-linux.so又是怎么找到的呢?其實(shí)不用找,ld-linux.so的位置是寫死在程序中的,gcc在編譯程序時就寫死在里面了。Gcc寫到程序中l(wèi)d-linux.so的位置是可以改變的,通過修改gcc的spec文件。
運(yùn)行時,ld-linux.so查找共享庫的順序
(1)ld-linux.so.6在可執(zhí)行的目標(biāo)文件中被指定,可用readelf命令查看
(2)ld-linux.so.6缺省在/usr/lib和lib中搜索;當(dāng)glibc安裝到/usr/local下時,它查找/usr/local/lib
(3)LD_LIBRARY_PATH環(huán)境變量中所設(shè)定的路徑
(4)/etc/ld.so.conf(或/usr/local/etc/ld.so.conf)中所指定的路徑,由ldconfig生成二進(jìn)制的ld.so.cache中
編譯時,ld-linux.so查找共享庫的順序
(1)ld-linux.so.6由gcc的spec文件中所設(shè)定
(2)gcc --print-search-dirs所打印出的路徑,主要是libgcc_s.so等庫。可以通過GCC_EXEC_PREFIX來設(shè)定
(3)LIBRARY_PATH環(huán)境變量中所設(shè)定的路徑,或編譯的命令行中指定的-L/usr/local/lib
(4)binutils中的ld所設(shè)定的缺省搜索路徑順序,編譯binutils時指定。(可以通過“l(fā)d --verbose | grep SEARCH”來查看)
(5)二進(jìn)制程序的搜索路徑順序?yàn)镻ATH環(huán)境變量中所設(shè)定。一般/usr/local/bin高于/usr/bin
(6)編譯時的頭文件的搜索路徑順序,與library的查找順序類似。一般/usr/local/include高于/usr/include
用 Linux 進(jìn)行動態(tài)鏈接
ELF映像
Linux 中的動態(tài)鏈接的共享庫的過程。當(dāng)用戶啟動一個應(yīng)用程序時,它們正在調(diào)用一個可執(zhí)行和鏈接格式(Executable and Linking Format,ELF)映像。內(nèi)核首先將 ELF 映像加載到用戶空間虛擬內(nèi)存中。然后內(nèi)核會注意到一個稱為 .interp 的 ELF 部分,它指明了將要被使用的動態(tài)鏈接器(例如:/lib/ld-linux.so)。
一個ELF頭在文件的開始,保存了路線圖(road map),描述了該文件的組織情況。sections保存著object 文件的信息,從連接角度看:包括指令,數(shù)據(jù),符號表,重定位信息等等。
?
使用 readelf 來顯示程序標(biāo)題
#readelf -l cpuid2
注意,ld-linux.so 本身就是一個 ELF 共享庫,但它是靜態(tài)編譯的并且不具備共享庫依賴項(xiàng)。當(dāng)需要動態(tài)鏈接時,內(nèi)核會引導(dǎo)動態(tài)鏈接(ELF 解釋器),該鏈接首先會初始化自身,然后加載指定的共享對象(已加載則不必)。接著它會執(zhí)行必要的再定位,包括目標(biāo)共享對象所使用的共享對象。
#readelf -l cpuid2.o
There are no program headers in this file.
ldd命令
Linux 提供了很多種查看和解析 ELF 對象(包括共享庫)的工具。其中最有用的一個當(dāng)屬 ldd命令,可以使用它來發(fā)現(xiàn)共享庫依賴項(xiàng)。
#ldd cpuid2
ldd所告訴您的是:該 ELF 映像依賴于 linux-gate.so(一個特殊的共享對象,它處理系統(tǒng)調(diào)用,它在文件系統(tǒng)中無關(guān)聯(lián)文件),GNU C庫(libc.so)以及 Linux 動態(tài)加載器(因?yàn)樗锩嬗泄蚕韼煲蕾図?xiàng))。
#ldd cpuid2.o
ldd: 警告: 你沒有執(zhí)行權(quán)限 `./cpuid2.o'
不是動態(tài)可執(zhí)行文件
readelf識別對象內(nèi)可再定位的C庫
readelf 命令是一個有很多特性的實(shí)用程序,它讓您能夠解析和讀取 ELF 對象。readelf 有一個有趣的用途,就是用來識別對象內(nèi)可再定位的項(xiàng)。對于我們這個簡單的程序來說,可以看到需要再定位的符號
#readelf -r cpuid2
從這個列表中,您可以看到各種各樣的需要再定位(到 libc.so)的 C庫調(diào)用。
#readelf -r cpuid2.o
readelf查看共享庫的依賴庫(NEEDED)和搜索名(SONAME)
#readelf -d cpuid2
#readelf -d cpuid2.o
無輸出
readelf查看ELF頭信息
#readelf -h cpuid2
#readelf -h cpuid2.o
附錄
#man readelf
?
objdump
objdump是用查看目標(biāo)文件或者可執(zhí)行的目標(biāo)文件的構(gòu)成的GCC工具
反匯編
#objdump -d cpuid2
對于其中的反匯編代碼
左邊是機(jī)器指令的字節(jié),右邊是反匯編結(jié)果。顯然,所有的符號都被替換成地址了, 注意沒有加$的數(shù)表示內(nèi)存地址,而不表示立即數(shù)。
objdump -x obj 以某種分類信息的形式把目標(biāo)文件的數(shù)據(jù)組織(被分為幾大塊)輸出 <可查到該文件的所有動態(tài)庫>
objdump -t obj 輸出目標(biāo)文件的符號表()
objdump -h obj 輸出目標(biāo)文件的所有段概括()
objdump -j .text/.data -S obj 輸出指定段的信息,大概就是反匯編源代碼把
objdump -S obj C語言與匯編語言同時顯示
更多參考
#man objdump
?
?
參考:Linux 動態(tài)庫剖析
http://www.ibm.com/developerworks/cn/linux/l-dynamic-libraries/
總結(jié)
以上是生活随笔為你收集整理的汇编语言使用C库函数和Linux动态链接的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用cat /proc/进程id/map
- 下一篇: gcc使用总结