使用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内存 movl %edx, 4(%edi) //位置写入3个寄存器的内容,这3个寄存器存储了 movl %ecx, 8(%edi) //CPU厂商信息的字符串(12字节) pushl $buffer //将buffer和output入栈,为printf提供参数 pushl $output call printf //调用C的printf函数 addl $8, %esp //将堆栈指针回滚8个字节,达到清除printf参数的目的 pushl $0 call exit
.asciz是在定义字符串的时侯在字符串结尾加上空字符(即C语言的\0),这样做的目的是为了让printf能读懂字符串。
.lcomm是在本地内存区域中声明固定长度的未初始化数据,这里初始化了12个字节的空间。
程序里buffer和output内存位置的内容是要向printf传递的参数值:
一个是"The processor Vender is '%s'\n"字符串;
另外一个是由cpuid返回结果(在ebx,edx,ecx三个寄存器中)填充的buffer。
需要通过堆栈来传递参数,所以在程序中使用
pushl $buffer
将参数入栈,printf获取参数是自右向左,即先buffer后output,所以要把buffer后入栈。
参数入栈之后,用call指令调用printf。
exit的情况同上,使用了一个参数--常数0。
汇编
#as -o cpuid2.o cpuid2.s
采用了动态连接的方式,所以C函数没有包含在可执行程序中,需要由另外的程序在运行时加载,ld不知道这个程序在哪里,所以我们还得手动指定
动态链接
#ld -dynamic-linker /lib/ld-linux.so.2 -lc -o cpuid2 cpuid2.o
#./cpuid2
输出
The processor Vendor ID is 'GenuineIntel'
GDB调试
# 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为一个数字,在不同的平台上名字也会不同。可以用ldd查看:
#ldd /bin/cat
最后一个没有=>的就是。其中第一个不是实际的库文件,你是找不到的,它是一个虚拟库文件用于和kernel交互。
ld-linux.so是专门负责寻找库文件的库。以cat为例,cat首先告诉ld-linux.so它需要libc.so.6这个库文件,ld-linux.so将按一定顺序找到libc.so.6库再给cat调用。
那ld-linux.so又是怎么找到的呢?其实不用找,ld-linux.so的位置是写死在程序中的,gcc在编译程序时就写死在里面了。Gcc写到程序中ld-linux.so的位置是可以改变的,通过修改gcc的spec文件。
运行时,ld-linux.so查找共享库的顺序
(1)ld-linux.so.6在可执行的目标文件中被指定,可用readelf命令查看
(2)ld-linux.so.6缺省在/usr/lib和lib中搜索;当glibc安装到/usr/local下时,它查找/usr/local/lib
(3)LD_LIBRARY_PATH环境变量中所设定的路径
(4)/etc/ld.so.conf(或/usr/local/etc/ld.so.conf)中所指定的路径,由ldconfig生成二进制的ld.so.cache中
编译时,ld-linux.so查找共享库的顺序
(1)ld-linux.so.6由gcc的spec文件中所设定
(2)gcc --print-search-dirs所打印出的路径,主要是libgcc_s.so等库。可以通过GCC_EXEC_PREFIX来设定
(3)LIBRARY_PATH环境变量中所设定的路径,或编译的命令行中指定的-L/usr/local/lib
(4)binutils中的ld所设定的缺省搜索路径顺序,编译binutils时指定。(可以通过“ld --verbose | grep SEARCH”来查看)
(5)二进制程序的搜索路径顺序为PATH环境变量中所设定。一般/usr/local/bin高于/usr/bin
(6)编译时的头文件的搜索路径顺序,与library的查找顺序类似。一般/usr/local/include高于/usr/include
用 Linux 进行动态链接
ELF映像
Linux 中的动态链接的共享库的过程。当用户启动一个应用程序时,它们正在调用一个可执行和链接格式(Executable and Linking Format,ELF)映像。内核首先将 ELF 映像加载到用户空间虚拟内存中。然后内核会注意到一个称为 .interp 的 ELF 部分,它指明了将要被使用的动态链接器(例如:/lib/ld-linux.so)。
一个ELF头在文件的开始,保存了路线图(road map),描述了该文件的组织情况。sections保存着object 文件的信息,从连接角度看:包括指令,数据,符号表,重定位信息等等。
使用 readelf 来显示程序标题
#readelf -l cpuid2
注意,ld-linux.so 本身就是一个 ELF 共享库,但它是静态编译的并且不具备共享库依赖项。当需要动态链接时,内核会引导动态链接(ELF 解释器),该链接首先会初始化自身,然后加载指定的共享对象(已加载则不必)。接着它会执行必要的再定位,包括目标共享对象所使用的共享对象。
#readelf -l cpuid2.o
There are no program headers in this file.
ldd命令
Linux 提供了很多种查看和解析 ELF 对象(包括共享库)的工具。其中最有用的一个当属 ldd命令,可以使用它来发现共享库依赖项。
#ldd cpuid2
ldd所告诉您的是:该 ELF 映像依赖于 linux-gate.so(一个特殊的共享对象,它处理系统调用,它在文件系统中无关联文件),GNU C库(libc.so)以及 Linux 动态加载器(因为它里面有共享库依赖项)。
#ldd cpuid2.o
ldd: 警告: 你没有执行权限 `./cpuid2.o'
不是动态可执行文件
readelf识别对象内可再定位的C库
readelf 命令是一个有很多特性的实用程序,它让您能够解析和读取 ELF 对象。readelf 有一个有趣的用途,就是用来识别对象内可再定位的项。对于我们这个简单的程序来说,可以看到需要再定位的符号
#readelf -r cpuid2
从这个列表中,您可以看到各种各样的需要再定位(到 libc.so)的 C库调用。
#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是用查看目标文件或者可执行的目标文件的构成的GCC工具
反汇编
#objdump -d cpuid2
对于其中的反汇编代码
左边是机器指令的字节,右边是反汇编结果。显然,所有的符号都被替换成地址了, 注意没有加$的数表示内存地址,而不表示立即数。
objdump -x obj 以某种分类信息的形式把目标文件的数据组织(被分为几大块)输出 <可查到该文件的所有动态库>
objdump -t obj 输出目标文件的符号表()
objdump -h obj 输出目标文件的所有段概括()
objdump -j .text/.data -S obj 输出指定段的信息,大概就是反汇编源代码把
objdump -S obj C语言与汇编语言同时显示
更多参考
#man objdump
参考:Linux 动态库剖析
http://www.ibm.com/developerworks/cn/linux/l-dynamic-libraries/