gcc, as, ld(转载)

1.本文不是教程,只是描述c语言(gcc环境),编译器,连接器,加载器,at&t汇编,ia32一些相关知识和笔记,很多需要深入的地方需要大家寻找相关的资料学习。如果发现错误,请留言或通知我jinglexy at yahoo dot com dot cn,这个是我的msn。打字不易,请转载时保留作者:http://www.cppblog.com/jinglexy

转自:http://www.cppblog.com/jinglexy/archive/2007/04/19/22298.html

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

编译成共享库:-fpic选项告诉连接器使用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没有寄存器,只提供相关的指令功能。

9.gas汇编工具:as(at&t风格)语法说明


使用$标识立即数


再寄存器前面加上%


源操作数在前,目标操作数在后


使用$获取变量地址


长跳转使用:ljmp $section, $offset

一个简单的汇编语言程序框架:

.section .data

……

.section .bss

……

.section .text

.globl _start

_start:

……

范例:

#cpuid2.s View the CPUID Vendor ID string using C library calls

.section .datatext

output:

.asciz "The processor Vendor ID is ‘%s‘\n"

.section .bss

.lcomm buffer, 12

.section .text

.globl _start

_start:

movl $0, %eax

cpuid

movl $buffer, %edi

movl %ebx, (%edi)

movl %edx, 4(%edi)

movl %ecx, 8(%edi)

pushl $buffer

pushl $output

call printf

addl $8, %esp

pushl $0

call exit

伪指令说明:


data


.ascii


定义字符串,没有\0结束标记


data


.asciz


有\0结束标记


data


.byte


字节


data


.int


32位


data


.long


32位


data


.shot


16位


bss


.lcomm


对于上面的例子是声明12字节的缓冲区,l标识local,仅当前汇编程序可用


bss


.comm


通用内存区域


data/text


.equ


.equ  LINUX_SYS_CALL, 0x80

movl $ LINUX_SYS_CALL, %eax

说明:equ不是宏而是常量,会占据数据/代码段空间

指令集说明:

<!--[if !supportMisalignedColumns]-->         <!--[endif]-->


movb/movw/movl


cmov


根据cf, of, pf, zf等标识位判断并mov


xchg


操作时会lock内存,非常耗费cpu时间


bswap


翻转寄存器中字节序


xadd


pushx, popx


pushad, popad


jmp


call


cmp


jz/jb/jne/jge


loop


addb/addw/addl


subb/subw/subl


dec/inc


mulb/muw/mull

无符号乘法


源操作数长度


目标操作数


目标位置


8位


al


ax


16位


ax


dx:ax


32位


eax


edx:eax


imul有符合乘法


divb/divw/divl

无符合除法

(被除数在eax中,除数在指令中给出)


被除数


被除数长



余数


ax


16位


al


ah


dx:ax


32位


ax


dx


edx:eax


64位


eax


edx


idiv有符合除法


sal/shl/sar/shr


移位


rol/ror/rcl/rcr


循环移位


leal


取地址:leal  output, %eax

等同于:movl  $output, %eax


rep


rep movsb      执行ecx次


lodsb/lodsw/lodsl

stosb/stosw/stosl


取存内存中的数据

               

gas程序范例(函数调用):

文件1:area.s定义函数area

# area.s - The areacircumference function

.section .text

.type area, @function

.globl area

area:

pushl %ebp

movl %esp, %ebp

subl $4, %esp

fldpi

filds 8(%ebp)

fmul %st(0), %st(0)

fmulp %st(0), %st(1)

fstps -4(%ebp)

movl -4(%ebp), %eax

movl %ebp, %esp

popl %ebp

ret

文件2:functest4.s调用者

# functest4.s - An example of using external functions

.section .data

precision:

.byte 0x7f, 0x00

.section .bss

.lcomm result, 4

.section .text

.globl _start

_start:

nop

finit

fldcw precision

pushl $10

call area

addl $4, %esp

movl %eax, result

pushl $2

call area

addl $4, %esp

movl %eax, result

pushl $120

call area

addl $4, %esp

movl %eax, result

movl $1, %eax

movl $0, %ebx

int $0x80

10.读连接器和加载器的一些笔记,感谢原作者colyli at gmail dot com,看了他翻译的lnl及写的一个os,受益匪浅。

如果不是很深入的研究连接器和加载器的话,了解一些原理就足够了。举个例子说明吧:

1 #include <unistd.h>

2 #include <stdlib.h>

3 #include <stdio.h>

4 #include <string.h>

5

6 int a = 1;

7 int main()

8 {

9         printf("value: %d\n", a);

10

11         return 0;

12 }

编译指令:gcc -c hello.c -o hello.o                   汇编

gcc -o hello hello.o                          编译

objdump -d hello.o                          反汇编目标文件

objdump -d hello                             反汇编可执行文件

比较两端结果:


objdump -d hello.o


objdump -d hello


00000000 <main>:

0:   55                    push  %ebp

1:   89 e5                  mov  %esp,%ebp

3:   83 ec 08    sub              $0x8,%esp

6:   83 e4 f0                and  $0xfffffff0,%esp

9:   b8 00 00 00 00  mov               $0x0,%eax

e:   83 c0             0f                   add    $0xf,%eax

11:   83 c0             0f                   add    $0xf,%eax

14:   c1 e8 04                   shr    $0x4,%eax

17:   c1 e0 04                   shl   $0x4,%eax

1a:               29 c4                     sub   %eax,%esp

1c:              83 ec 08        sub  $0x8,%esp

1f:              ff 35 00 00 00 00  pushl  0x0

25:   68 00 00 00 00   push              $0x0

2a:              e8 fc ff ff ff call  2b            <main+0x2b>

2f:               83 c4 10                   add $0x10,%esp

32:   b8 00 00 00 00  mov  $0x0,%eax

37:              c9                       leave

38:              c3                       ret


08048368 <main>:

8048368: 55      push   %ebp

8048369: 89 e5    mov    %esp,%ebp

804836b: 83 ec 08  sub    $0x8,%esp

804836e: 83 e4 f0             and    $0xfffffff0,%esp

8048371: b8 00 00 00 00  mov  $0x0,%eax

8048376: 83 c0 0f                   add  $0xf,%eax

8048379: 83 c0 0f       add              $0xf,%eax

804837c:            c1 e8 04       shr   $0x4,%eax

804837f:             c1 e0 04      shl   $0x4,%eax

8048382: 29 c4                     sub  %eax,%esp

8048384: 83 ec 08                  sub   $0x8,%esp

8048387: ff 35 94 95 04 08            pushl 0x8049594

804838d: 68 84 84 04 08             push $0x8048484

8048392:   e8 19 ff ff ff   call             80482b0

<[email protected]>

8048397: 83 c4            10       add  $0x10,%esp

804839a:             b8 00 00 00 00 mov $0x0,%eax

804839f:            c9                       leave

80483a0:            c3                       ret

80483a1:            90                      nop

80483a2:            90                      nop

80483a3:            90                      nop

简单说明:由于程序运行时访问内存,执行跳转都需要确切的地址。所以汇编处理的目标文件里面没有包含,而是把这个工作放到连接器中:即定位地址。

当程序需要动态链接到某个库上时,使用该库的got表动态定位跳转即可。

具体可以看colyli大侠的《链接器和加载器Beta 2》,及《从程序员角度看ELF》

11.连接器脚本ld—script(相关内容来自《GLD中文手册》)

ld --verbose查看默认链接脚本

ld把一定量的目标文件跟档案文件连接起来,并重定位它们的数据,连接符号引用.一般在编译一个程序时,最后一步就是运行ld。

实例1:

SECTIONS {       . = 0x10000;       .text : { *(.text) }       . = 0x8000000;       .data : { *(.data) }       .bss : { *(.bss) } }

注释:“.”是定位计数器,设置当前节的地址。

实例2:

floating_point = 0;     SECTIONS     {

. = ALIGN(4);       .text :         {           *(.text)            _etext = .;

PROVIDE(etext = .);     }

. = ALIGN(4);       _bdata = (. + 3) & ~ 3;       .data : { *(.data) }     }

注释:定义一个符合_etext,地址为.text结束的地方,注意源程序中不能在此定义该符合,否则链接器会提示重定义,而是应该象下面这样使用:

extern char _etext;

但是可以在源程序中使用etext符合,连接器不导出它到目标文件。

实例3:

SECTIONS {       outputa 0x10000 :         {         all.o         foo.o (.input1)         }       outputb :         {         foo.o (.input2)         foo1.o (.input1)         }       outputc :         {         *(.input1)         *(.input2)         }   }

这个例子是一个完整的连接脚本。它告诉连接器去读取文件all.o中的所有节,并把它们放到输出节outputa的开始位置处, 该输出节是从位置0x10000处开始的。从文件foo.o中来的所有节.input1在同一个输出节中紧密排列。 从文件foo.o中来的所有节.input2全部放入到输出节outputb中,后面跟上从foo1.o中来的节.input1。来自所有文件的所有余下的.input1和.input2节被写入到输出节outputc中。

示例4:连接器填充法则:

SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } }                    错误

SECTIONS { .text : { *(.text) ; LONG(1) } .data : { *(.data) } }           正确

示例5:VMA和LMA不同的情况

SECTIONS       {       .text 0x1000 : { *(.text) _etext = . ; }       .mdata 0x2000 :         AT ( ADDR (.text) + SIZEOF (.text) )         { _data = . ; *(.data); _edata = . ;  }       .bss 0x3000 :         { _bstart = . ;  *(.bss) *(COMMON) ; _bend = . ;}     }

程序:

extern char _etext, _data, _edata, _bstart, _bend;     char *src = &_etext;     char *dst = &_data;

/* ROM has data at end of text; copy it. */     while (dst &lt; &_edata) {       *dst++ = *src++;     }

/* Zero bss */     for (dst = &_bstart; dst&lt; &_bend; dst++)       *dst = 0;

示例6:linux-2.6.14/arch/i386/kernel $ vi vmlinux.lds.S

linux内核的链接脚本,自行分析吧,有点复杂哦。

时间: 2024-08-07 00:12:26

gcc, as, ld(转载)的相关文章

gcc和ld 中的参数 --whole-archive 和 --no-whole-archive

首先 --whole-archive 和 --no-whole-archive 是ld专有的命令行参数,gcc 并不认识,要通gcc传递到 ld,需要在他们前面加 -Wl,字串. --whole-archive 可以把 在其后面出现的静态库包含的函数和变量输出到动态库,--no-whole-archive 则关掉这个特性. 比如你要把 liba.a  libb.a libc.a 输出到 libabc.dll(或libabc.so)时应该这么写: libabc.dll:liba.c libb.a

2015-08-26: GCC编译选项(转载)

gcc提供了大量的警告选项,对代码中可能存在的问题提出警告,通常可以使用-Wall来开启以下警告:           -Waddress -Warray-bounds (only with -O2) -Wc++0x-compat           -Wchar-subscripts -Wimplicit-int -Wimplicit-function-declaration           -Wcomment -Wformat -Wmain (only for C/ObjC and un

GNU LD之一LMA和VMA

MIPS 处理器存储器结构 项目当中使用的是一颗MIPS CPU,存储空间是标准的MIPS内存分配,内存被划分为几个部分,概括如下: Boot room, boot code存储空间: iram, code 存储空间: dram,data存储空间: 也就是说code和data有各自独立的存储空间,分开放置. 我们平常用gcc和ld生成一个可执行文件的时候,例如在命令行输入gcc -o test test.c,生成的可执行文件是一个文件哦,也就是说code和data都在一份可执行文件里面.我们把这

【嵌入式开发】gcc 学习笔记(一) - 编译C程序 及 编译过程

一. C程序编译过程 编译过程简单介绍 : C语言的源文件 编译成 可运行文件须要四个步骤, 预处理 (Preprocessing) 扩展宏, 编译 (compilation) 得到汇编语言, 汇编 (assembly) 得到机器码, 连接 (linking) 得到可运行文件; -- 查看每一个步骤的编译细节 : "-E" 相应 预处理, "-S" 相应 编译, "-c" 相应 汇编, "-O" 相应 连接; -- 每一个步骤

gcc 学习笔记(一) - 编译C程序 及 编译过程

一. C程序编译过程 编译过程简介 : C语言的源文件 编译成 可执行文件需要四个步骤, 预处理 (Preprocessing) 扩展宏, 编译 (compilation) 得到汇编语言, 汇编 (assembly) 得到机器码, 连接 (linking) 得到可执行文件; -- 查看每个步骤的编译细节 : "-E" 对应 预处理, "-S" 对应 编译, "-c" 对应 汇编, "-O" 对应 连接; -- 每个步骤对应的工

王垠:完全用Linux工作 - imsoft.cnblogs

完全用Linux工作,抛弃windows 我已经半年没有使用 Windows 的方式工作了.Linux 高效的完成了我所有的工作. GNU/Linux 不是每个人都想用的.如果你只需要处理一般的事务,打游戏,那么你不需要了解下面这些了. 我不是一个狂热的自由软件份子,虽然我很喜欢自由软件.这篇文章也不是用来推行自由软件运动的,虽然我觉得自由软件运动是非常好的. 这篇文章也不是用来比较 Linux 和 Windows 内核效率,文件系统,网络服务的.我现在是作为一个用户而不是一个开发者来说话的,我

最简单的基于FFmpeg的移动端例子:IOS HelloWorld

本文记录IOS平台下基于FFmpeg的HelloWorld程序.该示例C语言的源代码来自于<最简单的基于FFMPEG的Helloworld程序>.相关的概念就不再重复记录了. IOS程序使用FFmpeg类库的说明 IOS应用程序使用FFmpeg类库的流程如下所示. 1. 编译FFmpeg类库 编译IOS的FFmpeg类库需要支持5种架构:armv7.armv7s.arm64.i386.x86_64.其中前面3个是给真机使用的,后面2个是给模拟器使用的.本文记录的FFmpeg类库还支持第三方类库

完全用Linux工作,抛弃windows

录一篇旧文 作者:王垠 我已经半年没有使用 Windows 的方式工作了.Linux 高效的完成了我所有的工作. GNU/Linux 不是每个人都想用的.如果你只需要处理一般的事务,打游戏,那么你不需要了解下面这些了. 我不是一个狂热的自由软件份子,虽然我很喜欢自由软件.这篇文章也不是用来推行自由软件运动的,虽然我觉得自由软件运动是非常好的. 这篇文章也不是用来比较 Linux 和 Windows 内核效率,文件系统,网络服务的.我现在是作为一个用户而不是一个开发者来说话的,我们的讨论是基于操作

自动编译当前目录下所有文件的Makefile

下面是我在一个项目中使用的Makefile. 脚本会自动搜索当前目录下所有子目录,并依据目录下的.c 和 .cxx生成对应的.o,最后生成应用application, 代码中删除了项目相关配置,如CFLAGS,LDFLAGS,CXXFLAGS中的gcc配置 欢迎转载,烦请添加链接,谢谢! 1 SHELL=/bin/sh 2 3 CC = $(CROSS_COMPILE)gcc 4 CXX = $(CROSS_COMPILE)g++ 5 LD = $(CROSS_COMPILE)ld 6 7 CF