1.本文不是教程,只是描述c语言(gcc环境),编译器,连接器,加载器,at&t汇编,ia32一些相关知识和笔记,很多需要深入的地方需要大家寻找相关的资料学习。如果发现错误,请留言或通知我jinglexy at yahoo dot com dot cn,这个是我的msn。打字不易,请转载时保留作者。
2.gcc安装的各个部分:
|
|
g++
|
c++编译器,链接时使用c++库
|
|
|
gcc
|
c编译器,链接时使用c库
|
|
|
cc1
|
实际的c编译器
|
|
|
cc1plus
|
实际的c++编译器
|
|
|
collect2
|
使用collect2产生特定的全局初始化代码,后台处理是传递参数给ld完成实际的链接工作。
|
|
|
crt0.o
|
初始化和结束代码
|
|
|
libgcc
|
平台相关的库
|
gcc安装需要的文件:
gcc-core-3.4.6.tar.gz2 gcc核心编译器,默认只包含c编译器
gcc-g++-3.4.6.tar.bz2 g++编译器
gcc-testsuite-3.4.6.tar.bz2 测试套件
./configure && make && make
install
3.binutils安装的各个部分
|
|
as
|
gnu汇编工具
|
|
|
gprof
|
性能分析工具
|
|
|
ld
|
gnu链接器
|
|
|
make
|
|
|
|
objcopy
|
目标文件从二进制格式翻译或复制到另一种
|
|
|
objdump
|
显示目标文件的各种信息
|
|
|
strings
|
显示文件的字符串
|
|
|
strip
|
去除符合表
|
|
|
readelf
|
分析elf并显示信息
|
链接器可以读写各种目标文件中的信息,通过BFD(binary file
descriptor)提供的工具实现,BFD定义了类似a.out, elf, coff等目标文件的格式。
4.gcc预处理程序
1)define指令
#可将传递的宏字符串化
##将两个名字连接成一个(注意不是连接成字符串)
例:#define TEST(ARGTERM) \
printf(“the term “ #ARGTERM
“is a string\n”)
使用__VA_ARGS__定义可变参数宏
例:#define err(...) fprintf(stderr,
__VA_ARGS)
err (“%s %d\n”, “error code
is”, 48);
为了消除无参数时的逗号,可以用下面方法定义:
# define err(...) fprintf(stderr, ##__VA_ARGS)
一种等同的方法是:
#define dprintf(fmt, arg...) printf(fmt, ##arg)
其他例:#define PASTE(a, b) a##b
2)error 和 warning指令
#error “y here?
bad boy!”
3)if, elif, else, endif指令
支持的运算符:加减乘除,位移,&&,||,!等
示例:#if defined (CONFIG_A) || defined
(CONFIG_B)
……
#endif
4)gcc预定义宏
|
|
__BASE_FILE__
|
完整的源文件名路径
|
|
|
__cplusplus
|
测试c++程序
|
|
|
__DATE__
|
|
|
|
__FILE__
|
源文件名
|
|
|
__func__
|
替代__FUNCTION__,__FUNCTION__以被GNU不推荐使用
|
|
|
__TIME__
|
|
|
|
__LINE__
|
|
|
|
__VERSION__
|
gcc版本
|
|
|
|
|
5)几个简单例子:
例1:
#define min(X, Y) \
(__extension__ ({typeof (X) __x = (X), __y = (Y); \
(__x < __y) ? __x : __y; }))
#define max(X, Y) \
(__extension__ ({typeof (X) __x = (X), __y = (Y); \
(__x > __y) ? __x : __y; }))
这样做的目的是消除宏对X,Y的改变的影响,例如:result = min(x++, --y); printf(x, y);
补充:圆括号定义的符合语句可以生成返回值,例:
result
= ({ int a = 5;
int
b;
b
= a + 3;
}); 将返回8
例2:
#define dprintfbin(buf, size) do{ int
i; \
printf("%s(%d)@", \
__FUNCTION__,
__LINE__); \
for(i
= 0; i < size - 1; i++){ \
if(0
== i % 16) \
printf("\n"); \
printf("0x%02x
", ((char*)buf)[i]); \
} \
printf("0x%02x\n",
((char*)buf)[i]); \
}while(0)
这个比较简单,不用解释了
例3:
#ifdef __cplusplus
extern "C"{
#endif
int foo1(void);
int foo2(void);
#ifdef __cplusplus
}
#endif
作用:在c++程序中使用c函数及库,c++编译程序时将函数名粉碎成自己的方式,在没有extern的情况下可能是_Z3_foo1,_Z3_foo2将导致连接错误,这里的extern表示在连接库时,使用foo1,foo2函数名。
5.gcc编译的一些知识
gcc
-E hello.c -o
hello.i 只预处理
gcc
-S hello.c -o
hello.s 只编译
gcc
-c -fpic first.c
second.c 编译成共享库:告诉连接器使用got表定位跳转指令,使加载器可以加载该动态库到任何地址(具体过程可在本文后面找到)
6.gcc对c语言的扩展
void fetal_error() __attribute__(noreturn); 声明函数:无返回值
__attribute__((noinline)) int foo1(){……} 定义函数:不扩展为内联函数
int getlim() __attribute__((pure, noinline)); 声明函数:不内联,不修改全局变量
void mspec(void) __attribute__((section(“specials”))); 声明函数:连接到特定节中
补充:除非使用-O优化级别,否则函数不会真正的内联。
其他属性:
|
函数
|
always_inline
|
|
|
函数
|
const
|
同pure
|
|
函数
|
constructor
|
加入到crt0调用的初始化函数表
|
|
函数
|
deprecated
|
无论何时调用函数,总是让编译器警告
|
|
函数
|
destructor
|
|
|
函数
|
section
|
放到命名的section中,而不是默认的.text
|
|
变量
|
aligned
|
分配该变量内存地址时对齐属性,例:
int value __attribute__((aligned(32)));
|
|
变量
|
deprecated
|
无论何时引用变量,总是让编译器警告
|
|
变量
|
packed
|
使数据结构使用最小的空间,例如:
typedef
struct zrecord{
char a;
int b __attribute((packed));
}zrecord_t;
变量b在内存中和a没有空隙
|
|
变量
|
section
|
同上,例:
int trigger __attribute__((section(“domx”)))
= 0;
|
|
类型
|
aligned
|
同上,例:
struc blockm{
char j[3];
}__attribute__((aligned(32)));
|
|
类型
|
deprecated
|
同上
|
|
类型
|
packed
|
同上
|
|
|
|
|
gcc内嵌函数:
void *__builtin_return_address(unsigned int
level);
void *__builtin_frame_address(unsigned int
leve);
以上两个函数可以用于回溯函数栈,如果编译器优化成noframe呢,谁愿意验证一下?
gcc使用__asm__, __typeof__, __inline__替代asm, typeof, inline。-std和-ansi会使后者失去功能。
标识符局部化,使用__label__标签:
int main(……){
{
__label__
jmp1;
goto
jmp1;
}
goto
jmp1; /* 错误:jmp1未定义 */
}
typeof的一些技巧:
|
|
char *chptr
|
a char point
|
|
|
typeof (*chptr) ch;
|
a char
|
|
|
typeof (ch) *chptr2;
|
a char point
|
|
|
typeof(chptr) chparray[10];
|
ten char pointers
|
|
|
typeof(*chptr) charray[10];
|
ten char
|
|
|
typeof (ch) charray2[10];
|
ten chars
|
7.objdump程序
|
|
-a
|
|
文档头文件信息
|
|
|
-d
|
|
可执行代码的反汇编
|
|
|
-D
|
|
反汇编可执行代码及数据
|
|
|
-f
|
|
完整文件头的内容
|
|
|
-h
|
|
section表
|
|
|
-p
|
|
目标格式的文件头内容
|
调试器呢?网上的gdb教程已足够的多,不再画蛇添足了。
8.平台IA32的一些知识
指令码格式:
|
指令前缀(0~4字节)
|
操作码(1~3字节)
|
可选修饰符(0~4字节)
|
可选数据元素(0~4字节)
|
指令前缀:较重要的有内存锁定前缀(smp系统中使用)
操作码:ia32唯一必须的部分
修饰符:使用哪些寄存器,寻址方式,SIB字节
数据元素:静态数值或内存位置
ia32比较重要的技术:指令预取,解码管线,分支预测,乱序执行引擎
(网络上可以找到很多相关的文章)
通用寄存器(8个32位):eax, ebx, ecx, edx, esi, edi, esp, ebp
端寄存器(6个16位):cs, ds, ss, es, fs, gs
指令指针(1个32位):eip
浮点寄存器(8个80位):形成一个fpu堆栈
控制寄存器(5个32位):cr0, cr1, cr2, cr3, cr4
较重要的是cr0:控制操作模式和处理器状态
cr3:内存分页表描述寄存器
调试寄存器(8个32位):
标识寄存器(1个32位):状态,控制,系统(共使用17位):陷阱,中断,进位,溢出等
说明:mmx使用fpu堆栈作为寄存器,sse, sse2, sse3没有寄存器,只提供相关的指令功能。