gcc动态库静态库文件生成使用及as汇编编译器使用

提示:本文为学习课程作业,主要记录学习过程,存在很多不足


一.可执行程序的装载

预处理:gcc -E -o hello.cpp hello.c -m32,这一步主要是进行一些宏的替换,得到的仍然是一个文本文件
编译:gcc -x cpp-ouput -S -o hello.s hello.cpp -m32,这一步会将c代码翻译成汇编语言
汇编:gcc -x assembler -c hello.s -o hello.o -m32,将编译阶段生成的汇编代码(.S文件)转变为目标
文件(.o),这一步得到的目标文件已经是ELF格式的二进制文件了,但还不能直接运行,需要和库文件链接在
一起才可以运行,默认使用动态库进行链接
链接:gcc -o hello-static hello.o -m32 -static,将多个目标文件(.o文件)链接成ELF格式的可执行文件。
此为转载,详情点击Linux中可执行程序的装载和执行


1.用gcc生成静态库和动态库

1.编辑生成例子程序 hello.h、hello.c 和 main.c

1.1创建目录
输入以下代码

mkdir test1      //创建test1目录
cd test1         //进入test1目录进行后续操作

1.2创建文件
输入以下代码

touch hello.c   //源程序
touch hello.h   //头文件
touch main.c    //主程序

1.3在文件里输入相关代码
hello.h

#ifndef HELLO_H
#define HELLO_H
void hello(const char*name);
#endif//HELLO_H

hello.c

#include<stdio.h>
void hello(const char*name)
{
    
    
printf("hello%s!\n",name)
}

main.c

#include"hello.h"
int main()
{
    
    
hello("everyone");
return 0;
}

2.将hello.c编译成.o文件

输入以下代码

gcc -c hello.c      
ls             //查看是否生成了hello.o文件

在这里插入图片描述

3.由.o文件创建静态库

输入以下代码

ar -crv libmyhello.a hello.o
ls

在这里插入图片描述

4.在程序中使用静态库

方法一:

gcc -o hello main.c -L. -lmyhello
./hello

在这里插入图片描述
方法二:

gcc main.c libmyhello.a -o hello
./hello

在这里插入图片描述
方法三:

gcc -c main.c                         //生成main.o
gcc -o hello main.o libmyhello.a     // 生成可执行文件
./hello

在这里插入图片描述
删除静态库文件测试公用函数是否连接到目标文件hello中

rm libmyhello.a
./hello

在这里插入图片描述

5.由.o文件创建动态库文件

gcc -shared -fPIC -o libmyhello.so hello.o
ls

在这里插入图片描述

6.在程序中使用动态库

gcc -o hello main.c -L. -lmyhello
./hello

在这里插入图片描述
出现错误,这里我们将文件 libmyhello.so 复制到目录/usr/lib 中

sudo mv libmyhello.so /usr/lib   //不加sudo有可能出现权限不够导致无法复制
./hello

在这里插入图片描述


2.静态库.a与.so库文件的生成与使用

1.编辑生成所需要的四个文件 A1.c 、 A2.c、 A.h、test.c

1.1创建目录
输入以下代码

mkdir test2      //创建test2目录
cd test2         //进入test1目录进行后续操作

1.2创建文件
输入以下代码

touch A1.c  
touch A2.c   
touch A.h   
touch test.c

1.3在文件里输入相关代码
A1.c:

#include <stdio.h>
void print1(int arg)
{
    
    
printf("A1 print arg:%d\n",arg);
}

A2.c:

#include <stdio.h>
void print2(char *arg)
{
    
    
printf("A2 printf arg:%s\n", arg);
}

A.h

#ifndef A_H
#define A_H
void print1(int);
void print2(char *);
#endif

test.c:

#include <stdlib.h>
#include "A.h"
int main()
{
    
    
print1(1);
print2("test");
exit(0);
}

2.静态库.a文件的生成与使用

2.1生成目标文件

gcc -c A1.c A2.c

2.2生成静态库.a文件

ar crv libafile.a A1.o A2.o

2.3使用.a库文件,创建可执行程序

gcc -o test test.c libafile.a
./test

在这里插入图片描述

3.共享库.so文件的生成与使用

3.1生成目标文件

gcc -c -fpic A1.c A2.c

3.2生成共享库.so文件

gcc -shared *.o -o libsofile.so

3.3使用.so文件,创建可执行程序

gcc -o test test.c libsofile.so
./test

出现错误,输入 ldd test,发现找不到对应的.so文件
在这里插入图片描述
在这里插入图片描述
3.4拷贝.so文件

sudo cp libsofile.so /usr/lib

继续执行即可运行程序
在这里插入图片描述


二.改编第一次作业程序生成静态库和动态库文件并进行比较

1.生成静态库文件

打开第一次作业目录,改编代码(添加x2y函数)

1.1改编main1.c,sub1.c,sub1.h文件代码,以及添加sub2.c文件
代码如下:

main1.c

#include"sub1.h"                    //调用头文件sub1.h
int main()
{
    
    
   int m=1,n=2;                     //定义m,n
   printf("%.3f\n",x2x(m,n));       //输出x2x运行结果(精确到小数点第三位)
   printf("%.3f\n",x2y(m,n));      //输出x2y运行结果(精确到小数点第三位)
   return 0;
}  

sub1.c

#include"sub1.h"                   
float x2x(int a,int b)
{
    
    
   float sum;                      //定义sum
   sum=a+b;                        
   return sum;                     //返回sum的值
}   

sub2.c

#include"sub1.h"                   
float x2y(int a,int b)
{
    
    
   float sum;                      
   sum=b-a;                        
   return sum;                     
}   

sub1.h

#include<stdio.h>                 //创建sub1.h头文件

    float x2x(int a,int b);       //单精度变量
    float x2y(int a,int b);

1.2 生成.o文件(由于之前已生成main.o,sub1.o文件,故我这里直接生成sub2.o文件)

gcc -c sub2.c

在这里插入图片描述

1.3 将x2x,x2y目标文件用ar工具生成一个.a静态库文件

ar -crv libsub.a sub1.o sub2.o

在这里插入图片描述

1.4 main目标文件与该静态库文件连接

gcc -o main1 main1.c -L. libsub.a

在这里插入图片描述

1.5 查看大小
在这里插入图片描述

2.生成动态库文件并比较大小

2.1 由.o文件创建动态库文件

gcc -shared -fPIC -o libsub.so sub1.o sub2.o

在这里插入图片描述

2.2 在程序中使用动态库

gcc -o main2 main1.c -L. libsub.so

在这里插入图片描述
存在错误,所以需要复制文件

sudo mv libsub.so /usr/lib

在这里插入图片描述

2.2 查看大小
在这里插入图片描述
观察可知,静态库文件比动态库文件稍大。


三.Gcc相关知识

1.Gcc常用编译代码

(1)addr2line:用来将程序地址转换成其所对应的程序源文件及所对应的代码
行,也可以得到所对应的函数。该工具将帮助调试器在调试的过程中定位对应的源代码位置。
(2)as:主要用于汇编
(3)ld:主要用于链接
(4)ar:主要用于创建静态库。
(5)ldd:可以用于查看一个可执行程序依赖的共享库
(6)objcopy:将一种对象文件翻译成另一种格式,譬如将.bin 转换成.elf、或
者将.elf 转换成.bin 等。
(7)objdump:主要的作用是反汇编。
(8)readelf:显示有关 ELF 文件的信息
(9)size:列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段、总大小
等。

2.Gcc编译过程

1.预处理
预处理的过程主要包括以下过程:
(1) 将所有的#define 删除,并且展开所有的宏定义,并且处理所有的条件预编
译指令,比如#if #ifdef #elif #else #endif 等。
(2) 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。
(3) 删除所有注释“//”和“/* */”。
(4) 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
(5) 保留所有的#pragma 编译器指令,后续编译过程需要使用它们。
2.编译
编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及
优化后生成相应的汇编代码。
3.汇编
汇编过程调用对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为.o
的目标文件中。由于每一个汇编语句几乎都对应一条处理器指令,因此,汇编相
对于编译过程比较简单,通过调用 Binutils 中的汇编器 as 根据汇编指令和处理
器指令的对照表一一翻译即可。
当程序由多个源代码文件构成时,每个文件都要先完成汇编工作,生成.o 目标
文件后,才能进入下一步的链接工作。注意:目标文件已经是最终程序的某一部
分了,但是在链接之前还不能执行。
4.链接
链接也分为静态链接和动态链接,其要点如下:
(1) 静态链接是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行
文件会比较大。链接器将函数的代码从其所在地(不同的目标文件或静态链
接库中)拷贝到最终的可执行程序中。为创建可执行文件,链接器必须要完
成的主要任务是:符号解析(把目标文件中符号的定义和引用联系起来)和
重定位(把符号定义和内存地址对应起来然后修改所有对符号的引用)。
(2) 动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统
中把相应动态库加载到内存中去。

3.分析 ELF 文件

1.ELF文件的段
ELF 文件格式如下图所示,位于 ELF Header 和 Section Header Table 之间的都
是段(Section)。一个典型的 ELF 文件包含下面几个段:
.text:已编译程序的指令代码段。
.rodata:ro 代表 read only,即只读数据(譬如常数 const)。
.data:已初始化的 C 程序全局变量和静态局部变量。
.bss:未初始化的 C 程序全局变量和静态局部变量。
.debug:调试符号表,调试器用此段的信息帮助调试。
2.反汇编 ELF
由于 ELF 文件无法被当做普通文本文件打开,如果希望直接查看一个 ELF 文件包
含的指令和数据,需要使用反汇编的方法。
使用 objdump -D 对其进行反汇编


四.as汇编编译器执行程序

1.安装nasm

在Ubuntu中输入

sudo apt install nasm

输入密码即可开始安装
在这里插入图片描述

2.使用nasm进行编译

2.1创建目录

在这里插入图片描述

2.2创建hello.asm并进行编译

在这里插入图片描述
代码如下:

; hello.asm 
section .data            ; 数据段声明
        msg db "Hello, world!", 0xA     ; 要输出的字符串
        len equ $ - msg                 ; 字串长度
section .text            ; 代码段声明
global _start            ; 指定入口函数
_start:                  ; 在屏幕上显示一个字符串
        mov edx, len     ; 参数三:字符串长度
        mov ecx, msg     ; 参数二:要显示的字符串
        mov ebx, 1       ; 参数一:文件描述符(stdout) 
        mov eax, 4       ; 系统调用号(sys_write) 
        int 0x80         ; 调用内核功能
                         ; 退出程序
        mov ebx, 0       ; 参数一:退出代码
        mov eax, 1       ; 系统调用号(sys_exit) 
        int 0x80         ; 调用内核功能

nasm -f elf64 hello.asm         //得到hello.o文件
ld -s -o hello hello.o          //生成可执行程序
./hello                         //执行程序

在这里插入图片描述

3.编译生成”hello world”C代码程序大小并与nasm进行对比

3.1创建test.c并进行编译

在这里插入图片描述
代码如下:

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

在这里插入图片描述
c代码大小
在这里插入图片描述
nasm代码大小
在这里插入图片描述
可知,nasm生成的而可执行程序比.c生成的可执行程序小很多


五.Linux第三方库函数

1.curses主要功能

curses是一个在Linux/Unix下广泛应用的图形函数库,作用是可以在终端内绘制简单的图形用户界面。

2.一些基本函数及功能

initscr():初始化curses库和ttty
(在开始curses编程之前,必须使用initscr()这个函数来开启curses模式)

endwin():关闭curses并重置tty
(结束curses编程时,最后调用的一个函数)

move(y,x):将游标移动至 x,y 的位置

getch():从键盘读取一个字元。(注意!传回的是整数值)

refresh():使屏幕按照你的意图显示。比较工作屏幕和真实屏幕的差异,然后refresh通过终端驱动送出那些能使真实屏幕于工作屏幕一致的字符和控制码。(工作屏幕就像磁盘缓存,curses中的大部分的函数都只对它进行修改)

3.以游客身份体验远古时代的 BBS

在 win10 系统中,“控制面板”–>“程序”—>“启用或关闭Windows功能”,启用 “telnet client” 和"适用于Linux的Windows子系统"(后面会使用)。 然后打开一个cmd命令行窗口,命令行输入 telnet bbs.newsmth.net

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
点击确定
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.安装curses库

在这里插入图片描述

curses函数库的头文件(curses.h)和库文件被分别安装在/usr/include/和/usr/lib/下

5.用gcc编译生成一个终端游戏

贪吃蛇游戏

5.1创建mysnake1.0.c文件并编译

在这里插入图片描述
代码链接点这里

5.2执行程序

cc mysnake1.0.c -lcurses -o mysnake1.0
./mysnake1.0

在这里插入图片描述


总结

本次作业需要完成的东西偏多,难度不是很高。不过每一个知识点都需要我们很好的掌握。通过这次作业,我学习到的东西也有很多。

猜你喜欢

转载自blog.csdn.net/aiwr_/article/details/109058672