linux的c语言编译链接过程(gcc,ld),出现没有这个文件或目录及段错误的解决

linux里一般我们会直接用:
gcc 工程文件 -o 输出文件
来直接生成可执行文件,这个过程中gcc自己完成了预处理,编译,汇编的工作,并调用了连接器ld进行汇编。那能否尝试把这个过程分步完成呢?

进行尝试:

建立项目:

创建一个main.c,代码如下:

#include<stdio.h>
void main(){    
    printf("hello world\n");
}

在这里插入图片描述

gcc预处理:

预处理会处理掉代码中所有“#”开头的命令,如include,define等等。
命令行输入:

预处理生成.i文件
在这里插入图片描述

打开.i文件,发现#命令消失,取而代之的是数百行文字。
在这里插入图片描述

gcc编译:

编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生产相应的汇编代码文件。
命令行输入:
在这里插入图片描述
编译生成.s文件:
在这里插入图片描述
打开.s文件,发现我们的代码已成为汇编模式:
在这里插入图片描述

gcc汇编:

汇编就是将汇编代码翻译成符合一定格式的机器代码,在Linux系统上一般表现为ELF目标文件(OBJ文件)。
命令行输入:
在这里插入图片描述
汇编生成.o文件:
在这里插入图片描述
.o文件已经是机器码,我们无法识别。

ld链接:

链接可以把.o文件和系统库的.o文件、.so库文件链接起来,最终生成可以在特定平台运行的可执行文件。
这一步比较复杂,因为一般不知道我们的文件用到了系统哪些库,所以先用编译器gcc直接生成可执行文件out,用ldd命令查看其用到的系统库:
(注:ldd只能识别隐式调用的动态链接库,不能识别显式链接的动态链接库,但ld命令只需要获取程序隐式调用的库文件即可)
在这里插入图片描述
把对应的库从系统目录下复制到当前目录(也可以ld命令直接写绝对路径或用-lc参数表示关联C库):
注意,不能复制符号链接,因为会因路径变更而失效,因此需要把符号链接对应的实际.so文件复制过来:
在这里插入图片描述
执行链接命令,其实对于这个简单的打印hello world程序,真正需要的只有libc-2.31.so这一个库,因为其中有printf函数的实现:另一个ld-linux-x86-64.so.2的功能下面会说。
在这里插入图片描述
报错无法找到_start,这是因为程序执行时真正入口并非main,而是_start,先不管,强行指定入口为main试试:
-e main表示指定程序的入口函数为main。
在这里插入图片描述
链接成功,尝试执行:
./out1
报错:没有这个文件或目录

这是因为由于手动链接,并未指定正确的程序解释器:
用readelf命令查看生成的out1文件读取的解释器:
在这里插入图片描述
发现出现需要的解释器及路径 /lib/ld64.so.1
我们进入这个路径,发现并没有ld64.so.1 ,再用ldd命令查看out1的依赖库:
在这里插入图片描述
发现是需要/lib/ld64.so.1这个库的,而上面查看的out的对应库为/lib64/ld-linux-x86-64.so.2,这其实就是两个文件要加载的解释器,两个解释器是同一个库文件,但不知为何手动链接生成的out1并非直接访问该库,而是想要使用/lib/ld64.so.1软链接。

解决办法:

方法一:
因此,把/lib64/ld-linux-x86-64.so.2这个文件建立一个软链接/lib/ld64.so.1:
在这里插入图片描述
执行后就有了/lib/ld64.so.1这个文件。

方法二:
在ld命令中直接指定解释器:
在这里插入图片描述
解决完毕后可以成功生成out1。
在这里插入图片描述

再执行out1:
在这里插入图片描述

发现出现段错误。

段错误解决

如果用gdb调试就会发现,报段错误是在main函数返回时,返回到了一个奇怪的地址0x1。这个地址是不合法的,因此出现段错误。

为什么会发生这个?
首先要了解,上文也说过,linux程序的入口并非是main函数,而是C库的_start函数,_start函数再调用__libc_start_main 函数再调用我们的main函数,这里就不详细叙述这个过程,只要简单知道,一般我们的链接器把main.o和C库链接起来时,生成的执行程序,会先进入名为_start的函数中,经过一系列操作再调用main函数,我们自己写的的程序功能结束后,main函数再return回_start中,_start函数正式终结整个进程。
那么出错的原因就简单了,我们是手动链接,gcc自动链接会链接_start并指定其为入口函数,但我们生成的out1中入口函数是main,main函数执行return后返回不到_start,因此在栈上取得了一个奇怪的地址0x1。这个地址应该是linux为保护程序设定的,地址无效并报段错误,以防跳到更加重要的位置引起重大错误。
那么该如何解决呢?可以思考,为什么_start函数的返回不会出现段错误,它返回到了哪里呢?其实程序执行不可能一直无限返回,必定有一个终点,这个终点就是_start,因为_start并没有调用return,而是使用了exit函数直接终结进程。
用 man 命令查看exit函数:
man exit:
在这里插入图片描述
发现其确实没有返回,说明是进程的终点。

因此改正段错误有两种方法,
1,要么_start函数,
2,让main使用exit。
对于方法一,可以先找到_start函数所在库文件,然后正常用ld命令该文件。这样生成程序基本完全和gcc直接编译链接产生的out一致。
但这里介绍一种取巧办法,即我们自己定义_start函数。
写一个start.c:

#include <stdlib.h>
void _start(){
    main(); //调用main
    exit(0);  //终止进程
}

编译:
在这里插入图片描述
然后把这个.o文件链入我们的程序:
在这里插入图片描述

成功执行,只不过这个_start函数是我们伪造的。

对于方法二,修改main.c:

#include<stdio.h> //printf头文件
#include <stdlib.h> //exit头文件
void main(){    
    printf("hello world\n");
    exit(0);
}

由于exit的实现也在libc-2.31.so中,因此不需额外链接库文件。
编译链接并执行:
在这里插入图片描述
成功执行。

  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Linux命令行中逐步运行C语言程序,包括以下步骤: 1. 编写C语言源代码,保存为以.c为后缀的文件,比如test.c。 2. 使用预处理器cpp对源代码进行预处理。在命令行中输入以下命令: ``` cpp test.c > test.i ``` 这将会把预处理后的代码输出到test.i文件中。 3. 使用编译器cc1对预处理后的代码进行编译。在命令行中输入以下命令: ``` cc1 test.i -o test.s ``` 这将会把汇编代码输出到test.s文件中。 4. 使用汇编器as将汇编代码转换为目标文件。在命令行中输入以下命令: ``` as test.s -o test.o ``` 这将会把目标文件输出到test.o文件中。 5. 使用链接ld将目标文件与所需的库文件链接成最终的可执行文件。在命令行中输入以下命令: ``` gcc test.o -o test ``` 这将会把可执行文件输出到test文件中。 在这些步骤中,gcc命令是一个集成了预处理、编译、汇编、链接等功能的工具,可以直接用它来编译C语言程序,如下所示: ``` gcc test.c -o test ``` 这个命令会将test.c源代码文件编译成可执行文件test,其中包含了预处理、编译、汇编和链接的所有过程。 ### 回答2: 在Linux命令行中逐步运行C语言程序涉及cpp、cc1、as和gcc这几个工具。 首先,cpp是C预处理器,它负责处理C语言程序中的预处理指令,例如#include和#define等,解释宏定义并将相应代码插入到程序中。要使用cpp,可以在命令行中输入cpp 文件名.c -o 文件名.i。这将把C源文件转换为预处理后的文件,命名为文件名.i。 接下来,cc1是GCC的前端编译器,它负责将预处理后的文件转换为汇编语言。要使用cc1,可以在命令行中输入gcc -S 文件名.i -o 文件名.s。这将把预处理后的文件转换为汇编语言源文件,命名为文件名.s。 然后,as是汇编器,在机器语言级别上将汇编语言源文件转换为目标文件。要使用as,可以在命令行中输入as 文件名.s -o 文件名.o。这将把汇编语言源文件转换为目标文件,命名为文件名.o。 最后,gcc是GNU编译器套件中的主要编译器,它将汇编语言源文件和其它必要的目标文件链接在一起,生成可执行文件。要使用gcc,可以在命令行中输入gcc 文件名.o -o 可执行文件名。这将把目标文件和其它必要的文件链接在一起,生成可执行文件,命名为可执行文件名。 综上所述,通过依次运行cpp、cc1、as和gcc这几个工具,我们可以在Linux命令行中逐步运行C语言程序。这些工具分别负责C语言程序的预处理、编译、汇编和链接过程,最终生成可执行文件。这种逐步运行的方式可以更好地理解编译原理和了解程序的编译过程。 ### 回答3: 在Linux命令行中,逐步运行C语言程序通常需要使用到cpp、cc1、as、gcc等命令。 首先,我们需要使用cpp命令对C源代码进行预处理。该命令会根据源文件中的宏定义、条件编译指令等对源代码进行处理,并生成一个新的C源代码文件。假设我们的源代码文件为main.c,我们可以使用如下命令进行预处理: cpp main.c > main.i 接下来,我们需要使用cc1命令将预处理后的C源文件生成汇编代码。该命令会将C源代码翻译为对应的汇编代码,并生成一个汇编文件。我们可以使用如下命令进行汇编: cc1 main.i -o main.s 然后,我们需要使用as命令将汇编文件转换为目标文件。该命令会将汇编代码转换为二进制机器码,并生成一个目标文件。我们可以使用如下命令进行汇编: as main.s -o main.o 最后,我们需要使用gcc命令将目标文件链接为可执行程序。该命令会将目标文件与所需的库文件进行链接,并生成一个可执行文件。我们可以使用如下命令进行链接gcc main.o -o main 执行以上步骤后,我们就可以在Linux命令行中逐步运行C语言程序了。可以通过运行./main命令来执行生成的可执行文件

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值