论坛首页 编程语言技术论坛

C: 第101章 入口函数

浏览 424 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2020-09-21  

编译链接

编译链接其实包含一系列过程。

 

还是以上面的例子为例来讲解编译链接,采用gcc编译器:

 

 

#include <stdio.h>

void main()
{
  printf("hello, c!\n");
}

 

可以将编译链接分开进行,通常我们可以将这两个过程一起完成。上面的那个例子:

直接编译链接生成可执行程序

>gcc maintest.c -o maintest

>.\maintest

hello, c!

我们可以查看这一次编译链接过程的细节:

>gcc maintest.c -o maintest -v

Using built-in specs.

 

COLLECT_GCC=gcc

 

COLLECT_LTO_WRAPPER=/usr/lib/gcc/i686-pc-cygwin/4.5.3/lto-wrapper.exe

 

Target: i686-pc-cygwin

 

Configured with: /gnu/gcc/releases/respins/4.5.3-3/gcc4-4.5.3-3/src/gcc-4.5.3/configure --srcdir=/gnu/gcc/releases/respins/4.5.3-3/gcc4-4.5.3-3/src/gcc-4.5.3 --prefix=/usr --exec-prefix=/usr --bindir=/usr/bin --sbindir=/usr/sbin --libexecdir=/usr/lib --datadir=/usr/share --localstatedir=/var --sysconfdir=/etc --datarootdir=/usr/share --docdir=/usr/share/doc/gcc4 -C --datadir=/usr/share --infodir=/usr/share/info --mandir=/usr/share/man -v --with-gmp=/usr --with-mpfr=/usr --enable-bootstrap --enable-version-specific-runtime-libs --libexecdir=/usr/lib --enable-static --enable-shared --enable-shared-libgcc --disable-__cxa_atexit --with-gnu-ld --with-gnu-as --with-dwarf2 --disable-sjlj-exceptions --enable-languages=ada,c,c++,fortran,java,lto,objc,obj-c++ --enable-graphite --enable-lto --enable-java-awt=gtk --disable-symvers --enable-libjava --program-suffix=-4 --enable-libgomp --enable-libssp --enable-libada --enable-threads=posix --with-arch=i686 --with-tune=generic --enable-libgcj-sublibs CC=gcc-4 CXX=g++-4 CC_FOR_TARGET=gcc-4 CXX_FOR_TARGET=g++-4 GNATMAKE_FOR_TARGET=gnatmake GNATBIND_FOR_TARGET=gnatbind --with-ecj-jar=/usr/share/java/ecj.jar

 

Thread model: posix

gcc version 4.5.3 (GCC)

 

COLLECT_GCC_OPTIONS='-o' 'maintest.exe' '-v' '-mtune=generic' '-march=i686' 

 

 /usr/lib/gcc/i686-pc-cygwin/4.5.3/cc1.exe -quiet -v -D__CYGWIN32__ -D__CYGWIN__ -Dunix -D__unix__ -D__unix -idirafter /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../include/w32api -idirafter /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/lib/../../include/w32api maintest.c -quiet -dumpbase maintest.c -mtune=generic -march=i686 -auxbase maintest -version -o /cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/cceNNRyv.s

 

GNU C (GCC) version 4.5.3 (i686-pc-cygwin)

        compiled by GNU C version 4.5.3, GMP version 4.3.2, MPFR version 3.0.1-p4, MPC version 0.8

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072

ignoring nonexistent directory "/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/include"

ignoring duplicate directory "/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/lib/../../include/w32api"

#include "..." search starts here:

#include <...> search starts here:

 /usr/local/include

 /usr/lib/gcc/i686-pc-cygwin/4.5.3/include

 /usr/lib/gcc/i686-pc-cygwin/4.5.3/include-fixed

 /usr/include

 /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../include/w32api

End of search list.

 

GNU C (GCC) version 4.5.3 (i686-pc-cygwin)

        compiled by GNU C version 4.5.3, GMP version 4.3.2, MPFR version 3.0.1-p4, MPC version 0.8

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072

Compiler executable checksum: 89d6774c1d510265da7d48b735ce61fb

 

COLLECT_GCC_OPTIONS='-o' 'maintest.exe' '-v' '-mtune=generic' '-march=i686'

 

 /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/bin/as.exe -v -o /cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/ccrMX9kq.o /cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/cceNNRyv.s

 

GNU assembler version 2.23.52 (i686-cygwin) using BFD version (GNU Binutils) 2.23.52.20130309

 

COMPILER_PATH=/usr/lib/gcc/i686-pc-cygwin/4.5.3/:/usr/lib/gcc/i686-pc-cygwin/4.5.3/:/usr/lib/gcc/i686-pc-cygwin/:/usr/lib/gcc/i686-pc-cygwin/4.5.3/:/usr/lib/gcc/i686-pc-cygwin/:/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/bin/

 

LIBRARY_PATH=/usr/lib/gcc/i686-pc-cygwin/4.5.3/:/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../:/lib/:/usr/lib/

 

COLLECT_GCC_OPTIONS='-o' 'maintest.exe' '-v' '-mtune=generic' '-march=i686'

 

 /usr/lib/gcc/i686-pc-cygwin/4.5.3/collect2.exe --wrap _Znwj --wrap _Znaj --wrap _ZdlPv --wrap _ZdaPv --wrap _ZnwjRKSt9nothrow_t --wrap _ZnajRKSt9nothrow_t --wrap _ZdlPvRKSt9nothrow_t --wrap _ZdaPvRKSt9nothrow_t -Bdynamic --dll-search-prefix=cyg --large-address-aware --tsaware -o maintest.exe /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../crt0.o /usr/lib/gcc/i686-pc-cygwin/4.5.3/crtbegin.o -L/usr/lib/gcc/i686-pc-cygwin/4.5.3 -L/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../.. /cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/ccrMX9kq.o -lgcc -lgcc_eh -lcygwin -luser32 -lkernel32 -ladvapi32 -lshell32 -lgcc -lgcc_eh /usr/lib/gcc/i686-pc-cygwin/4.5.3/crtend.o

 

可以看到这个编译链接过程还挺复杂,分成3个步骤,其中:

/usr/lib/gcc/i686-pc-cygwin/4.5.3/cc1.exe -quiet -v -D__CYGWIN32__ -D__CYGWIN__ -Dunix -D__unix__ -D__unix -idirafter /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../include/w32api -idirafter /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/lib/../../include/w32api maintest.c -quiet -dumpbase maintest.c -mtune=generic -march=i686 -auxbase maintest -version -o /cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/cceNNRyv.s

这段是对maintest.c进行汇编编译成汇编程序cceNNRyv.s。这里用到了一个cc1.exe工具,这个工具我们一般用不到,它是一个将C程序汇编编译成汇编程序的工具。

 

它的效果其实就跟下面的一样:

>gcc -S maintest.c -o maintest.s

生成的汇编程序如下:

 

	.file	"maintest.c"
	.def	___main;	.scl	2;	.type	32;	.endef
	.section .rdata,"dr"
LC0:
	.ascii "hello, c!\0"
	.text
.globl _main
	.def	_main;	.scl	2;	.type	32;	.endef
_main:
	pushl	%ebp
	movl	%esp, %ebp
	andl	$-16, %esp
	subl	$16, %esp
	call	___main
	movl	$LC0, (%esp)
	call	_puts
	leave
	ret
	.def	_puts;	.scl	2;	.type	32;	.endef

 

 

/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/bin/as.exe -v -o /cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/ccrMX9kq.o /cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/cceNNRyv.s

这段是将上面的汇编程序cceNNRyv.s编译成对象文件ccrMX9kq.o。通过as工具,as这个工具如果我们用过as汇编的话就对它很熟,它是一个汇编编译器。

 

我们上面的编译过程实际上还包括这两个过程:

1、将c程序汇编编译成汇编程序

2、将汇编程序编译成对象文件

也就是说:

gcc -c maintest.c -o maintest.o

实际上包括上面这两个步骤。

 

/usr/lib/gcc/i686-pc-cygwin/4.5.3/collect2.exe --wrap _Znwj --wrap _Znaj --wrap _ZdlPv --wrap _ZdaPv --wrap _ZnwjRKSt9nothrow_t --wrap _ZnajRKSt9nothrow_t --wrap _ZdlPvRKSt9nothrow_t --wrap _ZdaPvRKSt9nothrow_t -Bdynamic --dll-search-prefix=cyg --large-address-aware --tsaware -o maintest.exe /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../crt0.o /usr/lib/gcc/i686-pc-cygwin/4.5.3/crtbegin.o -L/usr/lib/gcc/i686-pc-cygwin/4.5.3 -L/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../.. /cygdrive/c/DOCUME~1/ADMINI~1/LOCALS~1/Temp/ccrMX9kq.o -lgcc -lgcc_eh -lcygwin -luser32 -lkernel32 -ladvapi32 -lshell32 -lgcc -lgcc_eh /usr/lib/gcc/i686-pc-cygwin/4.5.3/crtend.o

这段是将上面的ccrMX9kq.o链接成可执行程序maintest.exe。这里用到了一个collect2.exe工具,这个是一个链接工具。

 

这个工具我们一般也用不到,实际上我们单独链接的话,也不会用到这个工具,我们会通过ld工具或者直接通过gcc进行单独链接。但这个工具其实就是ld工具的一个封装,它用的气势就是ld工具来进行链接。当然,通过gcc进行单独链接最后也是通过ld工具来进行链接。

 

所以,如果已经编译了的话, 我们可以通过这个工具单独进行链接:

 

>gcc -c maintest.c -o maintest.o

 

>D:\sbin\lib\gcc\i686-pc-cygwin\4.5.3\collect2.exe --wrap _Znwj --wrap _Znaj --wrap _ZdlPv --wrap _ZdaPv --wrap _ZnwjRKSt9nothrow_t --wrap _ZnajRKSt9nothrow_t --wrap _ZdlPvRKSt9nothrow_t --wrap _ZdaPvRKSt9nothrow_t -Bdynamic --dll-search-prefix=cyg --large-address-aware --tsaware -o maintest.exe /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../crt0.o /usr/lib/gcc/i686-pc-cygwin/4.5.3/crtbegin.o -L/usr/lib/gcc/i686-pc-cygwin/4.5.3 -L/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../.. maintest.o -lgcc -lgcc_eh -lcygwin -luser32 -lkernel32 -ladvapi32 -lshell32 -lgcc -lgcc_eh /usr/lib/gcc/i686-pc-cygwin/4.5.3/crtend.o

 

>.\maintest

 

hello, c!

 

上面通过collect2.exe工具链接的时候指定了很多多余的选项以及链接库,很多都没有用到,去掉没有用到的,其实很简单:

>gcc -c maintest.c -o maintest.o

 

>D:\sbin\lib\gcc\i686-pc-cygwin\4.5.3\collect2.exe -o maintest.exe /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../crt0.o maintest.o -lcygwin -lkernel32

 

可以通过以下命令dump查看cygwin和crt0.o的内部实现:

$ objdump -d libcygwin.a

$ objdump -d /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../crt0.o

 

>.\maintest

hello, c!

 

 

通常如果工程简单的话,我们可以一次完成整个过程,如果复杂的话,我们会分两次或多次完成,简单来讲,它分为编译和链接两个过程。

 

如果将编译和链接分开的话,

编译

>gcc -c maintest.c -o maintest.o

 

链接

>gcc maintest.o -o maintest

>.\maintest

hello, c!

 

通过ld工具进行链接

 

>ld maintest.o -o maintest

这样会报错:

maintest.o:maintest.c:(.text+0xa): undefined reference to `__main'

maintest.o:maintest.c:(.text+0x16): undefined reference to `puts'

 

缺失的__main和puts实际上在cygwin链接库中,需要加上-lcygwin。同时mainCRTStartup入口函数在/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../crt0.o中,链接的时候需要将这个对象文件一起链入进去。另外还需要-lkernel32,链接的时候依赖这个库。

 

puts函数的实现:

Disassembly of section .text:

 

00000000 <_puts>:

   0:   ff 25 00 00 00 00       jmp    *0x0

   6:   90                      nop

   7:   90                      nop

 

t-d001504.o:     文件格式 pe-i386

 

所以,应该这样:

>gcc -c maintest.c -o maintest.o

 

>ld -o maintest.exe /usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../crt0.o maintest.o -lcygwin -lkernel32

这里其实就是直接将上面通过collect2.exe工具链接的时候,直接将collect2.exe替换为ld工具进行链接。

 

>.\maintest

hello, c!

 

 

论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics