kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

gcc include

本文介绍在linux中头文件的搜索路径,也就是说你通过include指定的头文件,linux下的gcc编译器它是怎么找到它的呢。在此之前,先了解一个基本概念。

头文件是一种文本文件,使用文本编辑器将代码编写好之后,以扩展名.h保存就行了。头文件中一般放一些重复使用的代码,例如函数声明、变量声明、常数定 义、宏的定义等等。当使用#include语句将头文件引用时,相当于将头文件中所有内容,复制到#include处。#include有两种写法形式, 分别是:

1
2
#include <> : 直接到系统指定的某些目录中去找某些头文件。
#include “” : 先到源文件所在文件夹去找,然后再到系统指定的某些目录中去找某些头文件。

#include文件可能会带来一个问题就是重复应用,如a.h引用的一个函数是某种实现,而b.h引用的这个函数却是另外一种实现,这样在编译的时候将会出现错误。所以,为了避免因为重复引用而导致的编译错误,头文件常具有:

1
2
3
4
#ifndef    LABEL
#define    LABEL
	//代码部分
#endif

的格式。其中LABEL为一个唯一的标号,命名规则跟变量的命名规则一样。

gcc寻找头文件的路径(按照1->2->3的顺序)

1.

在gcc编译源文件的时候,通过参数-I指定头文件的搜索路径,如果指定路径有多个路径时,则按照指定路径的顺序搜索头文件。命令形式如:“gcc -I /path/where/theheadfile/in sourcefile.c“,这里源文件的路径可以是绝对路径,也可以是相对路径。eg:
设当前路径为/root/test,include_test.c如果要包含头文件“include/include_test.h“,有两种方法:
1) include_test.c中#include “include/include_test.h”或者#include “/root/test/include/include_test.h",然后gcc include_test.c即可
2) include_test.c中#include <include_test.h>或者#include <include_test.h>,然后gcc –I include include_test.c也可

2.

通过查找gcc的环境变量C_INCLUDE_PATH/CPLUS_INCLUDE_PATH/OBJC_INCLUDE_PATH来搜索头文件位置。

3. 再找内定目录搜索,分别是
1
2
3
/usr/include
/usr/local/include
/usr/lib/gcc-lib/i386-linux/2.95.2/include

最后一行是gcc程序的库文件地址,各个用户的系统上可能不一样。
gcc在默认情况下,都会指定到/usr/include文件夹寻找头文件。

gcc还有一个参数:-nostdinc,它使编译器不再系统缺省的头文件目录里面找头文件,一般和-I联合使用,明确限定头文件的位置。在编译驱动模块时,由于非凡的需求必须强制GCC不搜索系统默认路径,也就是不搜索/usr/include要用参数-nostdinc,还要自己用-I参数来指定内核 头文件路径,这个时候必须在Makefile中指定。

4.

当#include使用相对路径的时候,gcc最终会根据上面这些路径,来最终构建出头文件的位置。如#include <sys/types.h>就是包含文件/usr/include/sys/types.h

c与汇编的关系

_start是汇编程序的入口,main是c程序的入口?
gcc 只是一个 外壳而不是真正的编译器,这真的c编译器是/usr/lib/gcc/i486-gun/4.3.2/cc1,gcc调用c编译器、汇编器和链接器完成c 代码的编译链接工作。/usr/lib/gcc/i486-linux-gun/4.3.2/collect2是链接器ld的外壳,它调用ld完成链接。

i main.c被cc1编译成汇编程序/tmp/ccRGDpua.s。
ii 这个汇编程序被as汇编成目标文件/tmp/ccidnZ1d.o
iii 这个目标文件连同另外几个目标文件(crt1.o,crti.o,crtbegin.o,crtend.o,crtn.o)一起链接成可执行文件 main。在链接过程中还用-l,选项指定一些库文件,有libc、libgcc、ligcc_s,其中有些库是共享库,需要动态链接,所以用 -dynamic-linker选项指定动态链接器是/lib/ld-linux.so.2

1
2
3
4
5
6
7
8
9
10
$ nm /usr/lib/crt1.o
00000000 R  _IO_stdin_used
00000000 D __data_start
                 U __libc_csu_fini
                 U __libc_csu_init
                 U __libc_start_main
00000000 R _fp_hw
00000000 T _start
00000000 W data_start
                 U main

U main 这一行表示main这个符号在crt1.o已经被引用了,但是还没有定义(Undefined),因此需要别的目标文件提供一个定义并且和crt1.o链接在一起。T_start表示在crt1.o中已定义为(text)。

c 程序的入口点其实是crt1.o提供的start,它先做一些初始化工作(启动例程,startup routine),然后调用我们编写的main函数。所以,main函数是程序的入口,不够准确。start才是真正的入口点,而main函数是被 _start调用的。

U __libc_start_main,这个符号在其他几个目标文件中也没有定义,所以链接生成可执行文件之后仍然是个未定义符号。事实上这个符号在 libc中定义,libc是一个共享库,它并不像其他目标文件一样链接到可执行文件main中,而是在运行时做动态链接:
i 操作系统在加载main这个程序时,首先看它有没有需要动态链接的未定义符号。
ii如果需要做动态链接,就查看这个程序指定了哪些共享库,以及用什么动态链接器来做动态链接。我们在链接时用-lc选项指定了共享库libc,用-dynamic-linker /lib/ld-linux.so.2 指定动态链接器,这些信息都会写到可执行文件中。
iii动态连接器加载共享库,在其中查找这些未定义符号的定义,完成链接过程。

c内联汇编

完整的内联汇编格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__asm__(asembler template
		:output operands
		: input operands
		: list of clobbered registers
		);

e.g.

#include <stdio.h>
int main(void)
{
	int a=10,b;
	__asm__("movl %1,%%eax\n\t"
			"movl %%eax,%0\n\t"
			:"=r"(b)  //把%0所代表的寄存器的值输出给变量b
			:"r"(a)       //告诉编译器分配一个寄存器保存变量a的值,作为汇编程序的输入,对应%1
			:"%eax"
	);
	printf("result:%d,%d\n",a,b);
	return 0;
}

在64位主机上编译产生32位的目标代码

64位平台跟32位平台有很大的不同,包括参数传递方式,指令集都有很大的变化,那怎么能够让它正常运行呢?利用 gcc的-m32参数编译产生32位的目标代码,而不是64位的目标代码,因为32位的目标代码可以运行在64位的主机上。

1
2
3
4
5
6
$ gcc -m32 manydots.s -o manydots
$ ./manydots 
How many dots do you want to see? 10
..........
$ file manydots
manydots: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.8, not stripped

可以看到,这样就okay了。
实际上,我们还可以分步来做:先汇编,后链接。这样可以减少目标代码的大小,先看看原来的大小。

1
2
$ wc -c manydots
6495 manydots

我们分步汇编、链接:

1
2
3
4
5
6
7
8
9
10
11
// 这个时候是需要一个默认的_start入口的,如果不指定,会默认设置一个程序入口地址,因为这个时候没有人给我们设置一个真正的入口_start了。
$ sed -i -e "s/main/_start/g" manydots.s 
$ as --32 manydots.s -o manydots.o
$ ld -m elf_i386 manydots.o -o manydots
$ wc -c manydots
1026 manydots
$ echo "6495-1026" | bc 
5469
$ ./manydots 
How many dots do you want to see? 10
..........

可以发现,这样也可以正常工作,不过目标减少了5469个字节。为什么会有这样的效果呢?资料[2]给出了详细的解释,如果感兴趣,可以研究一下。
对了,“as –32 manydots.s -o manydots.o”可以直接用“$ gcc -m32 -c manydots.s -o manydots.o” 来做,他们两个实际上做了同一个事情,你可以通过gcc的–verbose查看:

1
2
3
4
5
6
7
8
9
10
11
12
$ gcc --verbose -m32 -c manydots.s -o manydots.o
Using built-in specs.
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.3.1-9' --with-bugurl=file:///usr/share/doc/gcc-4.3/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --enable-nls --with-gxx-include-dir=/usr/include/c++/4.3 --program-suffix=-4.3 --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-mpfr --enable-cld --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.3.1 (Debian 4.3.1-9) 
COLLECT_GCC_OPTIONS='-v' '-m32' '-c' '-o' 'manydots.o' '-mtune=generic'
 as -V -Qy --32 -o manydots.o manydots.s
GNU assembler version 2.18.0 (x86_64-linux-gnu) using BFD version (GNU Binutils for Debian) 2.18.0.20080103
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.3.1/:/usr/lib/gcc/x86_64-linux-gnu/4.3.1/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.3.1/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.3.1/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.3.1/32/:/usr/lib/gcc/x86_64-linux-gnu/4.3.1/32/:/usr/lib/gcc/x86_64-linux-gnu/4.3.1/../../../../lib32/:/lib/../lib32/:/usr/lib/../lib32/:/usr/lib/gcc/x86_64-linux-gnu/4.3.1/:/usr/lib/gcc/x86_64-linux-gnu/4.3.1/:/usr/lib/gcc/x86_64-linux-gnu/4.3.1/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-m32' '-c' '-o' 'manydots.o' '-mtune=generic'

最后总结一下,在64位主机上编译产生32位目标代码的办法:

一、办法一:直接通过gcc汇编、链接

1、确保不要有重复的start入口,把start替换成main
2、用gcc加上-m32参数进行汇编和链接

二、办法二:分步汇编、链接

1、汇编的时候,用gcc加上-m32参数或者用as加上–32参数。
2、在链接的时候,用ld加上-m elf_i386参数。