“GUN软件”这个说法并不常见,它可能指的是与“GUN Project”相关的软件。GUN Project是一个开源软件项目,旨在创建一个完整的、自由的软件套件,以替代UNIX系统。GUN Project提供了许多开源软件,包括操作系统、编译器、文本编辑器、办公套件等。
然而,作为一个AI语言模型,我无法实时更新我的知识库,因此我可能无法了解所有最新的GUN软件。但是,我可以提供一些GUN Project中常见的软件类型和例子:
1. 操作系统:GUN Project提供了一个名为GNU Hurd的微内核操作系统,它是GNU操作系统的内核部分。
2. 编译器:GUN Project提供了GCC(GNU Compiler Collection),这是一套编译器,支持C、C++、Fortran等多种编程语言。
3. 文本编辑器:GUN Project提供了Emacs,这是一个功能强大的文本编辑器,具有丰富的扩展和自定义选项。
4. 办公套件:GUN Project提供了LibreOffice,这是一个免费的开源办公套件,包括文字处理、电子表格、演示文稿等应用程序。
5. 其他软件:GUN Project还提供了许多其他软件,如浏览器、邮件客户端、网络工具等。
请注意,由于GUN Project是一个不断发展的项目,可能还有许多其他软件和工具可供使用。如果您对特定的GUN软件感兴趣,建议您访问GUN Project的官方网站或相关社区,以获取最新的
相关内容:
GNU简介
GNU计划,又译为“革奴计划”,它的目标是创建一套完全自由的操作系统GNU,并且其内容软件完全以GPL方式发布。这个操作系统是GNU计划的主要目标,名称来自GNU's Not Unix!的递归缩写,因为GNU的设计类似Unix,但它不包含具著作权的Unix代码。作为操作系统,GNU的发展仍未完成,其中最大的问题是具有完备功能的内核尚未被开发成功。GNU的内核,称为Hurd,是自由软件基金会发展的重点,但是其发展尚未成熟。在实际使用上,多半使用Linux内核作为系统核心。

1、GCC
GCC原名为GNU C语言编译器(GNU C Compiler),只能处理C语言。但其很快扩展,变得可处理C++,后来又扩展为能够支持更多编程语言,如Fortran、Pascal、Objective -C、Java、Ada、Go以及各类处理器架构上的汇编语言等,所以改名GNU编译器套件(GNU Compiler Collection)。2、glibc
glibc是GNU发布的libc库,即c运行库。glibc是linux系统中最底层的api,几乎其它任何运行库都会依赖于glibc。glibc除了封装linux操作系统所提供的系统服务外,它本身也提供了许多其它一些必要功能服务的实现。glibc与libc的关系:glibc 和 libc 都是 Linux 下的 C 函数库。libc 是 Linux 下的 ANSI C 函数库;glibc 是 Linux 下的 GUN C 函数库。ANSI C 函数库是基本的 C 语言函数库,包含了 C 语言最基本的库函数。这个库可以根据头文件划分为 15 个部分,其中包括:
3、coreutils
coreutils 是GNU下的一个软件包,这个软件包中包含了很多程序,如ls、mv等程序。常用的如:
4、GDB
GDB(GNU symbolic debugger)是 GNU Project 调试器。GDB 可以做四种主要的事情(以及支持这些事情的其他事情)来帮助你捕获行为中的错误:- 启动你的程序,并指定可能影响其行为的所有内容。
- 使程序在指定条件下停止。
- 检查程序停止时发生的情况。
- 更改程序中的内容,以便你可以尝试纠正一个错误的影响,然后继续学习另一个错误。
5、binutils
GNU binutils是一组二进制工具集。包含的工具有:
6、其它
GNU系统包括很多软件包,还包括非GNU的自由软件。具体的介绍可以上gnu官网(http://www.gnu.org/software/)上查看:

GCC编译、链接
1、基本编译流程
使用gcc工具集将C语言源代码生成可执行程序需要经过4个步骤:预处理、编译、汇编、链接。如:
gcc
下面以一个实例来演示将C语言源代码生成可执行程序的过程。示例代码hello.c:#include <stdio.h>
int main(void)
{
printf("Hello gcc
");
return 0;
}
(1)预处理过程
使用预处理器cpp把源文件hello.c经过预处理生成hello.i文件,预处理用于将所有的#include头文件以及宏定义替换成其真正的内容。预处理的命令为:gcc -E hello.c -o hello.i
上述命令中-E是让编译器在预处理之后就退出,不进行后续编译过程;-o是指定输出文件名。预处理之后得到的仍然是文本文件。hello.i文件部分内容截图如下:
(2)编译过程
使用编译器将预处理文件hello.i编译成汇编文件hello.s。编译的命令为:gcc -S hello.i -o hello.s
上述命令中-S让编译器在编译之后停止,不进行后续过程;-o是指定输出文件名。汇编文件hello.s是文本文件,部分内容截图如下:
(3)汇编过程
使用汇编器将汇编文件hello.s转换成目标文件hello.o。汇编过程的命令为:gcc -c hello.s -o hello.o
上述命令中-c、-o让汇编器把汇编文件hello.s转换成目标文件hello.o。目标文件hello.o是二进制文件。这时候我们可以使用如下命令查看hello.o的格式:file hello.o
显示的内容:hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
可以看到,hello.o是个ELF(Executable and Linking Format,可执行链接格式)格式文件。另外,hello.o是个二进制文件,使用vscode打开可能会出现乱码,可以安装一个Binary插件。部分内容截图如下:
(4)链接过程
链接过程使用链接器将该目标文件与其他目标文件、库文件、启动文件等链接起来生成可执行文件。命令为:gcc hello.o -o hello
综上:
2、动态、静态链接
上一节的第(4)步的链接过程分为两种。一种是静态链接,另外一种是动态链接。它们的区别如:(1)静态链接
优点:代码装载速度快,执行速度略比动态链接库快。缺点:使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费。(2)动态链接
优点:生成的可执行文件较静态链接生成的可执行文件小。缺点:速度比静态链接慢;使用动态链接库的应用程序不是自完备的,需要依赖相关库。初学,理解不了?没关系,分享一个易懂的比喻:把链接过程看做我们平时学习时做笔记的过程。我们平时学习时准备一本笔记本专门记录我们的学习笔记,比如在某本书的某一页上看到一个很好很有用的知识,这时候我们有两种方法记录在我们的笔记本上,一种是直接把那一页的内容全部抄写一遍到笔记本上(静态链接);另一种是我们在笔记本上做个简单的记录(动态链接),比如写上:xxx知识点在《xxx》的xxx页。从这两种方法中我们可以很清楚地知道两种方式的特点,第一种方式的优点就是我们在复习的时候就很方便,不用翻阅其它书籍了,但是缺点也很明显,就是占用笔记本的空间很多,这种方法很快就把我们的笔记本给写满了。第二种方式的优点就是很省空间,缺点就是每当我们复习的时候,手头上必须备着相关的参考书籍,比如我们去教室复习的时候,就得背着一大摞书去复习,这样我们复习的效率可能就没有那么高了。这对应到我们的动态链接与静态链接上是不是就很好理解了。下面看看具体实例:文件1(main.c):#include "hello.h"
int main(void)
{
print_hello();
return 0;
}
文件2(hello.c):#include "hello.h"
void print_hello(void)
{
printf("hello world
");
}
文件3(hello.h):#ifndef __HELLO_H
#define __HELLO_H
#include <stdio.h>
void print_hello(void);
#endif
① 演示动态链接首先,将源文件生成目标文件(*.o),命令:gcc -c main.c hello.c
在Linux中,动态库的扩展名一般为.so。我们把上面生成的hello.o文件生成相应的动态库,命令:gcc -shared hello.o -o libhello.so
使用链接动态库的方式生成可执行程序,命令:gcc main.o -L. -lhello -o hello_d_lib_test
这里的-L.的含义是在搜索库文件时包含当前目录,-lhello的含义是链接名称为libhello.so的动态库。此时,运行hello_d_lib_test程序,可能会出现如下错误:./hello_d_lib_test: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory
这是因为找不到共享库文件libhello.so,加载失败。因为一般情况下Linux会在/usr/lib路径中搜索需要用到的库,而libhello.so库并不在这个路径下。解决方法有如下几种:- 把这个文件拷贝至/usr/lib路径下。
- .配置文件/etc/ld.so.conf中指定的动态库搜索路径。
- 临时生效,可以用 LD_LIBRARY_PATH 环境变量指定。
export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
这时候再次运行程序就可以正常运行了。② 演示静态链接静态库用ar工具来制作。ar是一个归档工具,用于建立、修改、提取归档文件(archive)。一个归档文件可以包含多个目标文件,也被称为静态库。在Linux下,静态库的扩展名一般为.a。把目标文件hello.o做成静态库,命令:ar -rv libhello.a hello.o
其中rv参数为组合参数,其中r参数表示当建立的模块名已经存在时,则覆盖同名模块,v参数用来显示附加信息,比如被处理的文件的名字。使用链接静态库的方法生成可执行程序,命令:gcc main.o -L. -lhello -o hello_s_lib_test
删除静态库之后,可执行程序也是能正常运行的。事实上,使用链接静态库的方式生成的可执行程序与直接使用目标文件生成的可执行程序没有区别。只是经过了静态库的链接,变为了一个文件,方便于调用、移植和保存。归档工具ar可以很方便地查看和删除归档文件中的成员。查看静态库libhello.a中的内容,命令:
GCC工具集的使用
1、ar工具的使用
基本使用如上面静态链接中的用法。2、addr2line工具的使用
addr2line可以将地址信息转化成函数名或行数。例如,如下代码运行会产生段错误:test.c:#include <stdio.h>
int main(void)
{
char *str = "hello";
str = 'a';
return 0;
}
首先,编译时加上-g参数,产生调试信息。gcc test.c -g -o test
运行会产生段错误Segmentation fault (core dumped)。此时会产生相关错误系统存于系统日志中。我们可以使用如下命令查看我们当前程序的错误信息:dmesg | grep test
此时会输出类似如下信息: test: segfault at 55f1d81186a4 ip 000055f1d811860d sp 00007ffc6fc1d080 error 7 in test_addr2line
此时借助addr2line工具可以查到产生错误的行号:addr2line -e test 55f1d81186a4
3、nm工具的使用
nm工具用于显示文件中的符号,可以用于各种ELF格式文件。ELF格式文件包括如下三种类型:
nm
其中,可以使用nn --help命令来查看支持的参数。其中,nm显示的符号类型如:
#include <stdio.h>
static int a = 1;
static int b;
void print_hello(void)
{
printf("hello
");
}
int main(void)
{
print_hello();
}
编译之后得到可执行程序test。执行如下命令查看test中的符号:nm test
输出结果如:0000000000201010 d a
0000000000201018 b b
# 省略部分内容......
000000000000064d T main
000000000000063a T print_hello
# 省略部分内容......
从输出结果可以知道,a是一个全局符号,该符号位于已初始化数据(RW Data)部分。b也是一个全局符号,该符号位于未初始化数据(BSS)部分。main符号与print_hello符号位于代码部分。4、strip工具的使用
strip工具用于删除文件中的符号。strip工具的使用方式:strip
其中,可以使用strip--help命令来查看支持的参数。我们以nm工具的演示代码来做演示。我们编译得到的可执行程序为test。没有执行strip之前,使用nm命令查看到的符号如:0000000000201010 d a
0000000000201018 b b
# 省略部分内容......
000000000000064d T main
000000000000063a T print_hello
# 省略部分内容......
使用ls -lh test命令查看test程序的大小为:8.2k。这时候执行如下命令删除test的符号部分,输出test_strip文件:strip test -o test_strip
使用nm命令查看test_strip文件是否有符号,显示结果为:nm: test_strip: no symbols
表示test_strip没有符号。使用ls -lh test_strip命令查看test_strip的大小为:6k。可见去掉符号表之后地程序变小了。在资源有限的系统中,可以使用这种方法为程序进行瘦身。5、readelf工具的使用
readelf工具用于显示ELF格式文件的信息。例如:readelf -h test
输出结果如:ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x530
Start of program headers: 64 (bytes into file)
Start of section headers: 6528 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 29
Section header string table index: 28
通过输出信息可以知道文件的类型、文件的格式等信息。6、objdump工具的使用
objdump工具用于显示目标文件的信息。objdump工具的使用方式:objdump
如:objdump -h hello.o
输出结果如:hello.o: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000013 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 0000000000000000 0000000000000000 00000053 2**0
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 0000000000000000 0000000000000000 00000053 2**0
ALLOC
3 .rodata 0000000c 0000000000000000 0000000000000000 00000053 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 0000002a 0000000000000000 0000000000000000 0000005f 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 0000000000000000 0000000000000000 00000089 2**0
CONTENTS, READONLY
6 .eh_frame 00000038 0000000000000000 0000000000000000 00000090 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
显示内容包含目标文件各个节的信息。7、strings工具的使用
strings工具用于查看文件中的字符串。strings工具的使用方式:strings
其中,可以使用strings--help命令来查看支持的参数。实例代码test.c:#include <stdio.h>
int main(void)
{
printf("11111
");
printf("22222
");
printf("33333
");
printf("44444
");
printf("55555
");
}
编译之后得到可执行程序test。执行如下命令查看test中的符号:strings test
输出结果如:# 省略部分内容......
11111
22222
33333
44444
55555
# 省略部分内容......
8、objcopy工具的使用
objcopy工具用于对目标文件的内容进行转换。objcopy工具的使用方式:objcopy
如使用如下命令可以删除可执行程序test中的.data段输出到test_rm:objcopy test -R .data test_rm
objcopy配合-R参数的使用可以达到类似strip工具的效果,给程序进行瘦身。GDB的基本使用
GDB(GNU Debugger)是一个强大的命令行调试工具。在Linux下进行开发,gdb工具是必知必会的工具之一。首先,看一下gdb常用的命令:

#include <stdio.h>
// 测试函数1
void test0(void)
{
int i = -1;
if (i = 0)
printf("i = %d
", i);
else if (i = 1)
printf("i = %d
", i);
else
printf("i = %d
", i);
}
// 测试函数2
void test1(void)
{
int a = {0,1,2,3,4,5,6,7,8,9};
int *p = &a;
int *p1 = (int*)(&a + 1);
printf("p = %d
", p);
printf("*(p1 - 1) = %d
", *(p1 - 1));
}
// 主函数
int main(int argc, char *argv)
{
test0();
test1();
return 0;
}
这个示例代码中有两个测试函数,其实也是两道经典易错的面试笔试题。大家可以先思考一下结果是什么。下面我们使用gdb来一步一步调试及分析。我们必须编译出带有调试信息(如行号等信息)的可执行文件才能使用gdb进行调试。在以上基础上加个-g参数即可生成调试信息。除此之外,我们编译时应不使用优化选项,若使用优化,则编译器会对程序进行一些优化,有可能会更改语句的顺序及优化一些变量,从而可能会导致程序执行流程与源码流程不匹配的情况。可以使用-Wall参数打开所有警告,我们的编译命令变为:gcc -g -Wall gdb_test.c -o gdb_test
使用上面的编译命令编译得到带调试信息的可执行程序gdb_test,有两种方法启动调试。一种方法是先输入gdb命令进入gdb环境,再输入file+可执行程序装入调试文件,即:

1、调试测试函数1
上面的测试函数1大家思考得出结果了吗?我们单步调试看看结果是怎么样的:① 在test1函数入口打个断点:

② 运行到断点处:

③ 单步往下执行:

if (expression)
statement
可以明确的是:如果对expression为真(非0),则执行statement。本题中,如if (i = 0)其实就等价于i = 0;
if (i)
显然这里的if语句的expression为假,不会执行statement。类似的if (i = 1)等价于i = 1;
if (i)
显然这里的if语句的expression为真,执行statement。平时在发现自己写的代码执行的流程异常时,不妨debug调试一下,一步一步地走,看程序是否按照自己设计的流程走,看是不是我们的执行逻辑设计错了。2、调试测试函数2
测试函数2也是一道极其经典的面试题目。不能一眼看出结果?没关系,我们一起调试分析一下。接着上面的流程,我们输出quit命令推出gdb环境,再重新进入调试test2。① 在test2函数入口打个断点:

② 运行到断点处:


③ 单步往下执行:
此时,我们来看一下,指针变量p的值、a数组里的值:


&a的值为0x7fffffffdda0
&a+1的值为0x7fffffffddc8
两个值相减得到40,正好是整个数组所占的字节数。而p1是一个整形指针,所以p1-1指向的就是往前偏移sizeof(int)个字节的地址,即a的地址(0x7fffffffddc4),所以*(p1 - 1)的值也就是a的值。最后我们再看一下&a往后的40个地址里的值都是些什么: