蚊章汇 - 编译
type: channel
link: https://linuxcn.bwsl.wang/tag/%E7%BC%96%E8%AF%91/
items: JVM 解释和编译指南
items: Linux 内核动手编译实用指南
items: 关于编译代码你应该知道的
items: Linux 上静态链接库工作原理
items: 如何在 Linux 上动态链接模块库
items: 编译代码时动态地链接库
items: 如何从源码编译 GNOME Shell 和应用
items: GNU C 编译器的程序员入门指南
items: 我最喜欢的 Go 构建选项
items: 任何人都可以通过这三个简单的步骤编译开源代码

JVM 解释和编译指南
type: item
date: 2023-11-06T17:36:37+08:00
description: 通过理解解释、即时编译和预先编译之间的区别，有效地使用它们。Java 是一种跨平台的编程语言。程序源代码会被编译为 字节码 bytecode ，然后字节码在运行时被转换为 机器码 machine code 。 解释器 interpreter 在物理机器上模拟出的抽象计算机上执行字节码指令。 即时 just-in-time （JIT）编译发生在运行期，而 预先 ahead-of-time （AOT）编译发生在构建期。本文将说明解释器、JIT 和 AOT 分别何时起作用，以及如何在 JIT 和 AOT 之间权衡。源代码、字节码、机器码应用程序通常是由 C、C++ 或 Java 等编程语言编写。用这些高级编程语言编写的指令集合称为源代码。源代码是人类可读的。要在目标机器上执行它，需要将源代码转换为机器可读的机器码。这个转换工作通常是由 编译器 compiler 来完成的。然而，在 Java 中，源代码首先被转换为一种中间形式，称为字节码。字节码是平台无关的，所以 Java 被称为平台无关编程语言。Java 编译器 javac 将源代码转换为字节码。然后解释器解释执行字节码。下面是一个简单的 Java 程序, Hello.java：//Hello.java public class Hello { public static void main(String[] args) { System.out.println("Inside Hello World!"); } } 使用 javac 编译它，生成包含字节码的 Hello.class 文件。$ javac Hello.java $ ls Hello.class Hello.java 现在，使用 javap 来反汇编 Hello.class 文件的内容。使用 javap 时如果不指定任何选项，它将打印基本信息，包括编译这个 .class 文件的源文件、包名称、公共和受保护字段以及类的方法。$ javap Hello.class Compiled from "Hello.java" public class Hello { public Hello(); public static void main(java.lang.String[]); } 要查看 .class 文件中的字节码内容，使用 -c 选项：$ javap -c Hello.class Compiled from "Hello.java" public class Hello { public Hello(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Inside Hello World! 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return } 要获取更详细的信息，使用 -v 选项：$ javap -v Hello.class 解释器，JIT 和 AOT解释器负责在物理机器上模拟出的抽象计算机上执行字节码指令。当使用 javac 编译源代码，然后使用 java 执行时，解释器在程序运行时运行并完成它的目标。$ javac Hello.java $ java Hello Inside Hello World! JIT 编译器也在运行期发挥作用。当解释器解释 Java 程序时，另一个称为运行时 分析器 profiler 的组件将静默地监视程序的执行，统计各部分代码被解释的次数。基于这些统计信息可以检测出程序的 热点 hotspot ，即那些经常被解释的代码。一旦代码被解释次数超过设定的阈值，它们满足被 JIT 编译器直接转换为机器码的条件。所以 JIT 编译器也被称为分析优化的编译器。从字节码到机器码的转换是在程序运行过程中进行的，因此称为即时编译。JIT 减少了解释器将同一组指令模拟为机器码的负担。AOT 编译器在构建期编译代码。在构建时将需要频繁解释和 JIT 编译的代码直接编译为机器码可以缩短 Java 虚拟机 Java Virtual Machine （JVM） 的 预热 warm-up 时间。（LCTT 译注：Java 程序启动后首先字节码被解释执行，此时执行效率较低。等到程序运行了足…

Linux 内核动手编译实用指南
type: item
date: 2023-10-04T19:53:00+08:00
description: 一份让你深入体验最新 Linux 内核编译过程的实操指南。出于各种原因，自行编译 Linux 内核可能引起你的兴趣。这些原因可能包括但不限于：测试一个比你目前的 Linux 发行版更新的内核版本采用一组不同的配置选项、驱动来构建内核学习者的好奇心 ?此指南将一步步指导你如何亲自编译 Linux 内核，包括你该运行哪些命令，为什么运行这些命令以及这些命令的执行效果。本文篇幅较长，所以请做好准备！? 诸如 Ubuntu 这样的发行版提供了更简单地安装主线 Linux 内核的方式。但本教程目标是从源码手动完成所有工作。此教程需要你付出时间、耐心以及丰富的 Linux 命令行使用经验。本文更注重亲身实践的体验。不管怎么说，我仍建议你在虚拟机或备用系统中尝试此冒险，而非在你的主系统上进行。前置准备在软件领域，构建任何事物都有两个基本要求：源代码构建依赖因此，作为预备环节，我们需要下载 Linux 内核的源码压缩包，并安装一些能让我们成功构建 Linux 内核的依赖项。Linux 版本导览在任何时刻，Freax Linux 内核都有四种“版本”。Linux 的这些 “版本”，按照开发流程的顺序是：linux-next 树： 所有准备合并到 Linux 代码库的代码首先被合并到 linux-next 树。它代表的是 Linux 内核最新也是“最不稳定”的状态。大多数 Linux 内核开发者和测试人员使用这个来提高代码质量，为 Linus Torvalds 的后续提取做准备。请谨慎使用！发布候选版（RC） / 主线版： Linus 从 linux-next 树抽取代码并创建一个初始发布版本。这个初始发布版本的测试版称为 RC（ 发布候选 Release Candidate ）版本。一旦 RC 版本发布，Linus 只会接受对它的错误修复和性能退化相关的补丁。基础这些反馈，Linus 会每周发布一个 RC 内核，直到他对代码感到满意。RC 发行版本的标识是 -rc 后缀，后面跟一个数字。稳定版： 当 Linus 觉得最新的 RC 版本已稳定时，他会发布最终的“公开”版本。稳定发布版将会维护几周时间。像 Arch Linux 和 Fedora Linux 这样的前沿 Linux 发行版会使用此类版本。我建议你在试用 linux-next 或任何 RC 版本之前，先试一试此版本。LTS 版本： 每年最后一个稳定版将会再维护 几年。这通常是一个较旧的版本，但它会 会积极地维护并提供安全修复。Debian 的稳定版本会使用 Linux 内核的 LTS 版版本。若想了解更多此方面的知识，可参阅 官方文档。本文将以当前可用的最新稳定版为例，编写此文时的 Linux 内核版本是 6.5.5。系统准备由于 Linux 内核使用 C 语言编写，编译 Linux 内核至少需要一个 C 编译器。你的计算机上可能还需要其他一些依赖项，现在是安装它们的时候了。? 这个指南主要聚焦于使用 GNU C 编译器（GCC）来编译 Linux 内核。但在未来的文章中（可能会深入介绍 Rust 的支持），我可能会介绍使用 LLVM 的 Clang 编译器作为 GCC 的替代品。不过，请注意，MSVC 并不适用。尽管如此，我仍期待有微软的员工为此发送修补程序集。我在瞎想啥？对于 Arch Linux 以及其衍生版本的用户，安装命令如下：sudo pacman -S base-devel bc coreutils cpio gettext initramfs kmod libelf ncurses pahole perl python rsync tar xz 对于 Debian 以及其衍生版本的用户，安装命令如下：sudo apt install bc binutils bison dwarves flex gcc git gnupg2 gzip libelf-dev libncurses5-dev libssl-dev make openssl pahole perl-base rsync tar xz-utils 对于 Fedora 以及其衍生版本的用户，安装命令如下：sudo dnf install binutils ncurses-devel \ /usr/include/{libelf.h,openssl/pkcs7.h} \ /usr/bin/{bc,bison,flex,gcc,git,gpg2,gzip,make,openssl,pahole,perl,rsync,tar,xz,zstd} 下载 Linux 内核源码请访问 kernel.org，在页面中寻找第一个 稳定 Stable 版本。你不会找不到它，因为它是最显眼的黄色方框哦 ?点击访问 kernel.org通…

关于编译代码你应该知道的
type: item
date: 2022-11-04T13:42:22+08:00
description: 用这个方便的捕鼠器比喻来理解编译代码。源代码必须要经过编译才能够运行程序，而对于开源软件，每个人都可以获取源代码。无论你是自己编写了代码，想要编译和运行它，还是下载了某人的项目来尝试它，了解如何通过 编译器 处理源代码，以及编译器如何处理这些代码，这都很有用。创建一个更好的捕鼠器一般情况我们不会将一个捕鼠器比作电脑，但不管你信不信，它确实与你正在使用的设备（手机或电脑）的 CPU 有一些相似之处。经典的捕鼠器（我说的不是 ?）有两种状态：打开或者释放。你可以认为 打开 是将捕鼠器设置好准备捕获老鼠，以及 释放 是捕鼠器被老鼠触发。某种意义上来说，捕鼠器就像是一台有鼠标的电脑。你可以想象一下这个代码，用一种虚构的语言来描述这个过程：if mousetrap == 0 then There's a mouse! else There's no mouse yet. end 换句话说，你可以基于捕鼠器的状态发现是否有老鼠（数据）。当然，捕鼠器不是万无一失的，有可能有一只老鼠在捕鼠器旁边，由于老鼠还没有触发捕鼠器，所以它的状态还是 打开 的。因此该程序可以进行改进，这都是非常典型的。开关总的来说，捕鼠器就是一个开关。你会在家里使用开关打开灯。可以从开关中获得许多信息。比如，人们会从你家灯的状态了解到你是否在家。你可以根据邻居家灯的状态来改变行为。如果邻居家所有的灯都熄灭了，那么请关掉你大声的音乐，因为人们可能已经上床睡觉了。CPU 也使用这样的逻辑，只不过乘以几个数量级，缩小到了微观级别。当 CPU 在特定寄存器上接收到电信号时，可以触发其他一些寄存器，然后触发另一个，以此类推。如果这些寄存器有特定的意义，那么就可以通信。也许激活同一主板上某处的芯片，或者使 LED 亮起，或者改变屏幕上的像素颜色。种瓜得瓜，种豆得豆。如果你真的想在多个位置而不是仅限于一处发现老鼠，但是你只有一个捕鼠器，那你应该开发一个应用才行。使用网络摄像头和一些基本的图像识别软件，你可以建立空厨房的模型，然后扫描变化。当老鼠进入厨房，在原先没有老鼠的图像上会有像素的变化。记录下这些数据，如果有无人机可以追踪老鼠并捕获会更好，这样就可以将老鼠赶出厨房了。这时，你通过打开和关闭信号的魔法，创造了一个更好的捕鼠器。编译器代码编译器将人们可阅读的代码转换成 CPU 可以理解的机器语言。这是非常复杂的过程，因为 CPU 非常复杂（甚至比捕鼠器更加复杂），同时因为该过程比严格“需要”的更加灵活。并不是所有的编译器都很灵活。有一些编译器只有一个目标，它们只会处理特定格式的代码文件，处理过程也因此而简单明了。幸运的是，现代的通用编译器并不简单。它们允许你编写不同语言的代码，也允许你用不同的方式链接库文件，并且可以生成运行在不同架构上的文件。GNU 编译器集合（GCC）的 gcc 编译器 --help 会输出超过 50 行的选项，LLVM 的 clang 编译器的 --help 输出超过 1000 行。GCC 指导手册的字数超过 10 万。当你在编译代码时会有很多选项。当然，大多数人并不需要知道所有的选项。我从未读过 GCC 的手册页，因为它们是针对 Objective-C、Fortran 以及我从未听说过的芯片架构的。不过我重视它将代码编译为不同的架构 —— 64 位或者 32 位 —— 的能力，以及在其他行业已经落后的计算机上运行开源软件的能力。编译生命周期同样重要的是，理解编译代码的不同阶段。这是一个简单的 C 语言程序的生命周期：带有宏定义的 C 源代码 .c 文件，用 cpp 预处理为 .i 文件。扩展了宏定义的 C 源代码 .i 文件，会被 gcc 转译成 .s 文件。以汇编语言写的文本文件 .s 文件被汇编为目标 .o 文件。带有 CPU 指令的二进制目标代码，以及其他目标文件和库 *.o 文件，以内存区域无关的偏移量，使用 ld 链接以生成可执行文件。最终的二进制文件要么包含所有需要的目标，要么设置以动态链接库 *.so 文件加载。你可以试试这个简单示例（可能需要对库路径做一些调整）：$ cat << EOF >> hello.c #include int main(void) { printf("hello world\n"); return 0; } EOF $ cpp hello.c > hello.i $ gcc -S hello.i $ as -o hello.o hello.s $ ld -static -o hello \ -L/usr/lib64/gcc/x86_64-slackware-linux/5.5.0/ \ /usr/lib64/crt1.o /usr/lib64/crti.o hello.o \ /usr/lib64/crtn.o --s…

Linux 上静态链接库工作原理
type: item
date: 2022-07-12T20:05:36+08:00
description: 学习如何用静态链接库将多个 C 目标文件结合到一个单个的可执行文件之中。使用 C 编写的应用程序时，通常有多个源码文件，但最终你需要编译成单个的可执行文件。你可以通过两种方式来完成这项工作：通过创建一个 静态 static 库 或 一个 动态 dynamic 库（也被称为 共享 shared 库）。从创建和链接的方式来看，它们是两种不同类型的库。选择使用哪种方式取决于你的的具体场景。在 上一篇文章 中，我演示了如何创建一个动态链接的可执行文件，这是一种更通用的方法。在这篇文章中，我将说明如何创建一个静态链接的可执行文件。使用静态库链接器 链接器 linker 是一个命令，它将一个程序的多个部分结合在一起，并为它们重新组织内存分配。链接器的功能包括：整合一个程序的所有的部分计算出一个新的内存组织结构，以便所有的部分组合在一起恢复内存地址，以便程序可以在新的内存组织结构下运行解析符号引用链接器通过这些功能，创建了一个名称为可执行文件的一个可运行程序。静态库是通过复制一个程序中的所有依赖库模块到最终的可执行镜像来创建的。链接器将链接静态库作为编译过程的最后一步。可执行文件是通过解析外部引用、将库例程与程序代码结合在一起来创建的。创建目标文件这里是一个静态库的示例以及其链接过程。首先，创建带有这些函数识别标志的头文件 mymath.h :int add(int a, int b); int sub(int a, int b); int mult(int a, int b); int divi(int a, int b); 使用这些函数定义来创建 add.c 、sub.c 、mult.c 和 divi.c 文件。我将把所有的代码都放置到一个代码块中，请将其分为四个文件，如注释所示：// add.c int add(int a, int b){ return (a+b); } //sub.c int sub(int a, int b){ return (a-b); } //mult.c int mult(int a, int b){ return (a*b); } //divi.c int divi(int a, int b){ return (a/b); } 现在，使用 GCC 来生成目标文件 add.o 、sub.o 、mult.o 和 divi.o：（LCTT 校注：关于“ 目标文件 object file ”，有时候也被称作“对象文件”，对此，存在一些译法混乱情形，称之为“目标文件”的译法比较流行，本文采用此译法。）$ gcc -c add.c sub.c mult.c divi.c -c 选项跳过链接步骤，而只创建目标文件。创建一个名称为 libmymath.a 的静态库，接下来，移除目标文件，因为它们不再被需要。（注意，使用一个 trash 命令比使用一个 rm 命令更安全。）$ ar rs libmymath.a add.o sub.o mult.o divi.o $ trash *.o $ ls add.c divi.c libmymath.a mult.c mymath.h sub.c 现在，你已经创建了一个名称为 libmymath 的简单数学示例库，你可以在 C 代码中使用它。当然，也有非常复杂的 C 库，这就是他们这些开发者来生成最终产品的工艺流程，你和我可以安装这些库并在 C 代码中使用。接下来，在一些自定义代码中使用你的数学库，然后链接它。创建一个静态链接的应用程序假设你已经为数学运算编写了一个命令。创建一个名称为 mathDemo.c 的文件，并将这些代码复制粘贴至其中：#include <mymath.h> #include <stdio.h> #include <stdlib.h> int main() { int x, y; printf("Enter two numbers\n"); scanf("%d%d",&x,&y); printf("\n%d + %d = %d", x, y, add(x, y)); printf("\n%d - %d = %d", x, y, sub(x, y)); printf("\n%d * %d = %d", x, y, mult(x, y)); if(y==0){ printf("\nDenominator is zero so can't perform division\n"); exit(0); }else{ printf("\n%d / %d = %d\n", x, y, divi(x, y)); return 0; } } 注意：第一行是一个 include 语句，通过名称来引用你自己的 libmymath 库。针对 mathDemo.c 创建一个名称为 …

如何在 Linux 上动态链接模块库
type: item
date: 2022-07-11T02:26:07+08:00
description: 学习如何用动态链接库将多个 C 目标文件结合到一个单个的可执行文件之中。当使用 C 编程语言编写一个应用程序时，你的代码通常有多个源文件代码。最终，这些文件必须被编译到一个单个的可执行文件之中。你可以通过创建静态或动态库（后者也被称为 共享 shared 库）来实现这一点。这两种类型的库在创建和链接的方式上有所不同。两者都有缺点和优点，这取决于你的使用情况。动态链接是最常见的方法，尤其是在 Linux 系统上。动态链接会保持库模块化，因此，很多应用程序可以共享一个库。应用程序的模块化也允许单独更新其依赖的共享库。在这篇文章中，我将演示动态链接是如何工作的。在后期的文章中，我将演示静态链接。链接器 链接器 linker 是一个命令，它将一个程序的数个部分结合在一起，并为它们重新组织内存分配。链接器的功能包括：整合一个程序的所有的部分计算出一个新的内存组织结构，以便所有的部分组合在一起恢复内存地址，以便程序可以在新的内存组织结构下运行解析符号引用链接器通过这些功能，创建了一个名为 可执行文件 executable 的可以运行的程序。在你创建一个动态链接的可执行文件前，你需要一些用来链接的库，和一个用来编译的应用程序。准备好你 最喜欢的文本编辑器 并继续。创建目标文件首先，创建带有这些函数签名的头文件 mymath.h ：int add(int a, int b); int sub(int a, int b); int mult(int a, int b); int divi(int a, int b); 使用这些函数定义来创建 add.c 、sub.c 、mult.c 和 divi.c 文件。我将把所有的代码都放置到一个代码块中，请将其分为四个文件，如注释所示：// add.c int add(int a, int b){ return (a+b); } //sub.c int sub(int a, int b){ return (a-b); } //mult.c int mult(int a, int b){ return (a*b); } //divi.c int divi(int a, int b){ return (a/b); } 现在，使用 GCC 来创建目标文件 add.o、sub.o、mult.o 和 divi.o ：（LCTT 校注：关于“ 目标文件 object file ”，有时候也被称作“对象文件”，对此，存在一些译法混乱情形，称之为“目标文件”的译法比较流行，本文采用此译法。）$ gcc -c add.c sub.c mult.c divi.c -c 选项跳过链接步骤，并且只创建目标文件。创建一个共享的目标文件在最终的可执行文件的执行过程中将链接动态库。在最终的可执行文件中仅放置动态库的名称。实际上的链接过程发生在运行时，在此期间，可执行文件和库都被放置到了主内存中。除了可共享外，动态库的另外一个优点是它减少了最终的可执行文件的大小。在一个应用程序最终的可执行文件生成时，其使用的库只包括该库的名称，而不是该库的一个多余的副本。你可以从你现有的示例代码中创建动态库：$ gcc -Wall -fPIC -c add.c sub.c mult.c divi.c 选项 -fPIC 告诉 GCC 来生成 位置无关的代码 position-independent code （PIC）。-Wall 选项不是必需的，并且与代码的编译方式是无关的。不过，它却是一个有价值的选项，因为它会启用编译器警告，这在排除故障时是很有帮助的。使用 GCC ，创建共享库 libmymath.so ：$ gcc -shared -o libmymath.so add.o sub.o mult.o divi.o 现在，你已经创建了一个简单的示例数学库 libmymath.so ，你可以在 C 代码中使用它。当然，也有非常复杂的 C 库，这就是他们这些开发者来生成最终产品的工艺流程，你和我可以安装这些库并在 C 代码中使用。接下来，你可以在一些自定义代码中使用你的新数学库，然后链接它。创建一个动态链接的可执行文件假设你已经为数学运算编写了一个命令。创建一个名称为 mathDemo.c 的文件，并将这些代码复制粘贴至其中：#include <mymath.h> #include <stdio.h> #include <stdlib.h> int main() { int x, y; printf("Enter two numbers\n"); scanf("%d%d",&x,&y); printf("\n%d + %d = %d", x, y, add(x, y)); printf("\n%d - %d = %d", x, y, sub(x, y)…

编译代码时动态地链接库
type: item
date: 2022-06-09T17:46:35+08:00
description: 编译软件在你如何运行你的系统方面给你很大的灵活性。LD_LIBRARY_PATH 变量，以及 GCC 的 -L 和 -l 选项，是这种灵活性的组成部分。编译软件是开发者经常做的事情，在开源世界中，一些用户甚至选择自己动手。Linux 播客 Dann Washko 称源码为“通用包格式”，因为它包含了使一个应用在任何平台上运行所需的所有组件。当然，并不是所有的源码都是为所有的系统编写的，所以它只是在目标系统的子集内是“通用”的，但问题是，源码是非常灵活的。有了开源，你可以决定代码的编译和运行方式。当你在编译代码时，你通常要处理多个源文件。开发人员倾向于将不同的类或模块放在不同的文件中，这样它们可以被单独维护，甚至可能被不同的项目使用。但当你编译这些文件时，许多文件会被编译成一个可执行文件。这通常是通过创建共享库来完成的，然后从可执行文件中动态链接回它们。这样可以通过保持模块化功能的外部性来保持可执行文件的小型化，并确保库可以独立于使用它们的应用而被更新。在编译过程中定位一个共享对象当你 用 GCC 编译 时，你通常需要在你的工作站上安装一个库，以便 GCC 能够定位到它。默认情况下，GCC 假定库在系统库路径中，例如 /lib64 和 /usr/lib64。然而，如果你要链接到一个你自己的尚未安装的库，或者你需要链接到一个没有安装在标准位置的库，那么你必须帮助 GCC 找到这些文件。有两个选项对于在 GCC 中寻找库很重要：-L（大写字母 L）在 GCC 的搜索位置上增加一个额外的库路径。-l（小写字母 L）设置你要链接的库的名字。例如，假设你写了一个叫做 libexample.so 的库，并且你想在编译你的应用 demo.c 时使用它。首先，从 demo.c 创建一个对象文件：$ gcc -I ./include -c src/demo.c -I 选项在 GCC 搜索头文件的路径中增加了一个目录。在这个例子中，我假设自定义头文件在一个名为 include 的本地目录中。-c 选项防止 GCC 运行链接器，因为这个任务只是为了创建一个对象文件。结果如下：$ ls demo.o include/ lib/ src/ 现在你可以使用 -L 选项为你的库设置一个路径，然后进行编译：$ gcc -L`pwd`/lib -o myDemo demo.o -lexample 注意，-L 选项在 -l 选项之前。这很重要，因为如果在你告诉 GCC 查找非默认库之前没有将 -L 添加到 GCC 的搜索路径中，GCC 就不知道要在你的自定义位置上搜索。编译成功了，但当你试图运行它时，却出现了问题：$ ./myDemo ./myDemo: error while loading shared libraries: libexample.so: cannot open shared object file: No such file or directory 用 ldd 排除故障ldd 工具可以打印出共享对象的依赖关系，它在排除类似问题时很有用：$ ldd ./myDemo linux-vdso.so.1 (0x00007ffe151df000) libexample.so => not found libc.so.6 => /lib64/libc.so.6 (0x00007f514b60a000) /lib64/ld-linux-x86-64.so.2 (0x00007f514b839000) 你已经知道定位不到 libexample，但 ldd 输出至少确认了它对工作库的期望位置。例如，libc.so.6已经被定位，ldd 显示其完整路径。LD\_LIBRARY\_PATHLD_LIBRARY_PATH 环境变量 定义了库的路径。如果你正在运行一个依赖于没有安装到标准目录的库的应用程，你可以使用 LD_LIBRARY_PATH 添加到系统的库搜索路径。有几种设置环境变量的方法，但最灵活的是在运行命令前放置环境变量。看看设置 LD_LIBRARY_PATH 对 ldd 命令在分析一个“损坏”的可执行文件时的作用：$ LD_LIBRARY_PATH=`pwd`/lib ldd ./ linux-vdso.so.1 (0x00007ffe515bb000) libexample.so => /tmp/Demo/lib/libexample.so (0x0000... libc.so.6 => /lib64/libc.so.6 (0x00007eff037ee000) /lib64/ld-linux-x86-64.so.2 (0x00007eff03a22000) 这也同样适用于你的自定义命令：$ LD_LIBRARY_PATH=`pwd`/lib my…

如何从源码编译 GNOME Shell 和应用
type: item
date: 2022-06-02T02:05:55+08:00
description: 这是一篇如何从源码编译 GNOME 的快速指南，包括 Shell、mutter 和一些原生应用。在编译之前，你需要确保一些事情，因为以下编译直接来自 Gitlab 的主分支，其中包含一些开发包。通常，你可以选择在任何 Linux 发行版中编译。但是我建议使用 Fedora Rawhide（Fedora 的开发分支，用于将来的发布）。另外，请勿在稳定系统中尝试此操作。因为操作可能出错，所以你可能最终得到损坏的系统。总而言之，你需要以下内容来从源码编译 GNOME。测试环境（虚拟机 或测试系统）。Fedora Rawhide 发行版（推荐，从此处下载）。确保你的发行版是最新的。你已登录 X.org 会话。我不建议你在 Wayland 会话中进行编译，因为你会遇到问题。从源码编译 GNOMEGNOME 桌面是一个基于其功能的软件包集合。Linux 发行版的桌面组件工作于窗口管理器和 shell 之下。因此，对于 GNOME，我将首先编译 mutter – 它是 GNOME Shell 的窗口管理器。然后进行 GNOME Shell 的编译。最后，我将编译一些原生应用。我将使用 meson 构建系统进行编译。meson 是一个漂亮的构建系统，快速且用户友好。编译 mutter打开终端并安装 GNOME Shell 和 mutter 所需的软件包。sudo dnf build-dep mutter gnome-shell 在主目录（或你想要的任何地方）中创建演示目录。cd ~ mkdir demo cd demo 从 Gitlab 克隆 mutter 的主分支。git clone https://gitlab.gnome.org/GNOME/mutter 进入克隆目录，然后使用以下 meson 命令来准备构建文件。默认情况下，meson 使用 /usr/local 用于构建文件。但是，你也可以使用前缀开关将输出重定向到特定文件夹（如下所示）。cd mutter meson _build --prefix=/usr 使用以下命令在构建完成时，将 mutter 安装在到系统中。sudo ninja install -C _build 编译 GNOME ShellGNOME Shell 和其他软件包的编译方法类似。首先，从 GitLab 克隆 GNOME Shell 主仓库，然后进行编译和安装。你可以按照下面的命令依次进行。在 GNOME Shell 中，你需要两个依赖项。它们是 asciidoc 和 sassc 。请在构建 GNOME Shell 之前安装它们。sudo dnf install asciidoc sudo dnf install sassc 安装完这些依赖项后，按照下面的命令来构建和安装 GNOME Shell。在运行这个命令之前，请确保你回到 demo 文件夹（我在第一步创建的）。git clone https://gitlab.gnome.org/GNOME/gnome-shellcd gnome-shellmeson _build --prefix=/usrsudo ninja install -C _build 运行 GNOME Shell编译完成后，你可以尝试重新启动 GNOME Shell 来查看来自主分支的变化。在重启之前，正如我之前提到的，确保你处于 X.Org 会话中。按 ALT+F2 并输入 r。然后按回车键。这个命令将重启 GNOME Shell。恭喜你! 你已经成功地编译了 GNOME Shell 和 Mutter。现在，是时候编译一些 GNOME 原生应用了。编译 GNOME 原生应用这些步骤对于 GNOME 或任何应用的所有源码都是一样的。你需要改变仓库的名字。因此，这里有一些编译必要的 GNOME 原生应用的命令示例。Files（Nautilus）git clone https://gitlab.gnome.org/GNOME/nautilus/cd gnome-shellmeson _build --prefix=/usrsudo ninja install -C _build GNOME 软件商店git clone https://gitlab.gnome.org/GNOME/gnome-software/cd gnome-shellmeson _build --prefix=/usrsudo ninja install -C _build GNOME 控制中心git clone https://gitlab.gnome.org/GNOME/gnome-control-center/cd gnome-shellmeson _build --prefix=/usrsudo ninja inst…

GNU C 编译器的程序员入门指南
type: item
date: 2022-05-30T19:19:50+08:00
description: 带你一窥生成二进制文件步骤的幕后，以便在出现一些错误时，你知道如何逐步解决问题。C 语言广为人知，深受新老程序员的好评。使用 C 语言编写的源文件代码，使用了标准的英语术语，因而人们可以方便阅读。然而，计算机只能理解二进制代码。为将代码转换为机器语言，你需要使用一种被称为 编译器 compiler 的工具。最常见的编译器是 GCC（ GNU 编译器集 GNU Compiler Collection ）。编译过程涉及到一系列的中间步骤及相关工具。安装 GCC为验证在你的系统上是否已经安装了 GCC，使用 gcc 命令：$ gcc --version 如有必要，使用你的软件包管理器来安装 GCC。在基于 Fedora 的系统上，使用 dnf ：$ sudo dnf install gcc libgcc 在基于 Debian 的系统上，使用 apt ：$ sudo apt install build-essential 在安装后，如果你想查看 GCC 的安装位置，那么使用：$ whereis gcc 演示使用 GCC 来编译一个简单的 C 程序这里有一个简单的 C 程序，用于演示如何使用 GCC 来编译。打开你最喜欢的文本编辑器，并在其中粘贴这段代码：// hellogcc.c #include <stdio.h> int main() { printf("Hello, GCC!\n"); return 0; } 保存文件为 hellogcc.c ，接下来编译它：$ ls hellogcc.c $ gcc hellogcc.c $ ls -1 a.out hellogcc.c 如你所见，a.out 是编译后默认生成的二进制文件。为查看你所新编译的应用程序的输出，只需要运行它，就像你运行任意本地二进制文件一样：$ ./a.out Hello, GCC! 命名输出的文件文件名称 a.out 是非常莫名其妙的，所以，如果你想具体指定可执行文件的名称，你可以使用 -o 选项：（LCTT 译注：注意这和最近 Linux 内核废弃的 a.out 格式无关，只是名字相同，这里生成的 a.out 是 ELF 格式的 —— 也不知道谁给起了个 a.out 这破名字，在我看来，默认输出文件名就应该是去掉了 .c 扩展名后的名字。by wxy）$ gcc -o hellogcc hellogcc.c $ ls a.out hellogcc hellogcc.c $ ./hellogcc Hello, GCC! 当开发一个需要编译多个 C 源文件文件的大型应用程序时，这种选项是很有用的。在 GCC 编译中的中间步骤编译实际上有四个步骤，即使在简单的用例中 GCC 自动执行了这些步骤。 预处理 Pre-Processing ：GNU 的 C 预处理器（cpp）解析头文件（#include 语句），展开 宏 macros 定义（#define 语句），并使用展开的源文件代码来生成一个中间文件，如 hellogcc.i。 编译 Compilation ：在这个期间中，编译器将预处理的源文件代码转换为指定 CPU 架构的汇编代码。由此生成是汇编文件使用一个 .s 扩展名来命名，如在这个示例中的 hellogcc.s 。 汇编 Assembly ：汇编程序（as）将汇编代码转换为目标机器代码，放在目标文件中，例如 hellogcc.o 。 链接 Linking ：链接器（ld）将目标代码和库代码链接起来生成一个可执行文件，例如 hellogcc 。在运行 GCC 时，可以使用 -v 选项来查看每一步的细节：$ gcc -v -o hellogcc hellogcc.c 手动编译代码体验编译的每个步骤可能是很有用的，因此在一些情况下，你不需要 GCC 完成所有的步骤。首先，除源文件文件以外，删除在当前文件夹下生成的文件。$ rm a.out hellogcc.o $ ls hellogcc.c 预处理器首先，启动预处理器，将其输出重定向为 hellogcc.i ：$ cpp hellogcc.c > hellogcc.i $ ls hellogcc.c hellogcc.i 查看输出文件，并注意一下预处理器是如何包含头文件和扩展宏中的源文件代码的。编译器现在，你可以编译代码为汇编代码。使用 -S 选项来设置 GCC 只生成汇编代码：$ gcc -S hellogcc.i $ ls hellogcc.c hellogcc.i hellogcc.s $ cat hellogcc.s 查看汇编代码，来看看生成了什么。汇编使用你刚刚所生成的汇编代码来创建一个目标文件：$ as -o hellogcc.o hellogcc.s $ ls hellogcc.c hellogcc.…

我最喜欢的 Go 构建选项
type: item
date: 2022-05-01T01:21:00+08:00
description: 这些方便的 Go 构建选项可以帮助你更好地理解 Go 的编译过程。学习一门新的编程语言最令人欣慰的部分之一，就是最终运行了一个可执行文件，并获得预期的输出。当我开始学习 Go 这门编程语言时，我先是阅读一些示例程序来熟悉语法，然后是尝试写一些小的测试程序。随着时间的推移，这种方法帮助我熟悉了编译和构建程序的过程。Go 的构建选项提供了更好地控制构建过程的方法。它们还可以提供额外的信息，帮助把这个过程分成更小的部分。在这篇文章中，我将演示我所使用的一些选项。注意：我使用的“ 构建 build ”和“ 编译 compile ”这两个词是同一个意思。开始使用 Go我使用的 Go 版本是 1.16.7。但是，这里给出的命令应该也能在最新的版本上运行。如果你没有安装 Go，你可以从 Go 官网 上下载它，并按照说明进行安装。你可以通过打开一个命令提示符，并键入下面的命令来验证你所安装的版本：$ go version 你应该会得到类似下面这样的输出，具体取决于你安装的版本：go version go1.16.7 linux/amd64 基本的 Go 程序的编译和执行方法我将从一个在屏幕上简单打印 “Hello World” 的 Go 程序示例开始，就像下面这样：$ cat hello.go package main import "fmt" func main() { fmt.Println("Hello World") } 在讨论更高级的选项之前，我将解释如何编译这个 Go 示例程序。我使用了 build 命令，后面跟着 Go 程序的源文件名，本例中是 hello.go，就像下面这样：$ go build hello.go 如果一切工作正常，你应该看到在你的当前目录下创建了一个名为 hello 的可执行文件。你可以通过使用 file 命令验证它是 ELF 二进制可执行格式（在 Linux 平台上）。你也可以直接执行它，你会看到它输出 “Hello World”。$ ls hello hello.go $ file ./hello ./hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped $ ./hello Hello World Go 提供了一个方便的 run 命令，以便你只是想看看程序是否能正常工作，并获得预期的输出，而不想生成一个最终的二进制文件。请记住，即使你在当前目录中没有看到可执行文件，Go 仍然会在某个地方编译并生成可执行文件并运行它，然后把它从系统中删除。我将在本文后面的章节中解释。$ go run hello.go Hello World $ ls hello.go 更多细节上面的命令就像一阵风一样，一下子就运行完了我的程序。然而，如果你想知道 Go 在编译这些程序的过程中做了什么，Go 提供了一个 -x 选项，它可以打印出 Go 为产生这个可执行文件所做的一切。简单看一下你就会发现，Go 在 /tmp 内创建了一个临时工作目录，并生成了可执行文件，然后把它移到了 Go 源程序所在的当前目录。$ go build -x hello.go WORK=/tmp/go-build1944767317 mkdir -p $WORK/b001/ << snip >> mkdir -p $WORK/b001/exe/ cd . /usr/lib/golang/pkg/tool/linux_amd64/link -o $WORK \ /b001/exe/a.out -importcfg $WORK/b001 \ /importcfg.link -buildmode=exe -buildid=K26hEYzgDkqJjx2Hf-wz/\ nDueg0kBjIygx25rYwbK/W-eJaGIOdPEWgwC6o546 \ /K26hEYzgDkqJjx2Hf-wz -extld=gcc /root/.cache/go-build /cc \ /cc72cb2f4fbb61229885fc434995964a7a4d6e10692a23cc0ada6707c5d3435b-d /usr/lib/golang/pkg/tool/linux_amd64/buildid -w $WORK \ /b001/exe/a.out # internal mv $WORK/b001/exe/a.out hello rm -r $WORK/b001/ 这有助于解决在程序运行后却在当前目录下没有生成可执行文件的谜团。使用 -x 显示可执行文件确实在 /tmp 工作目录下创建并被执行了。然而，与 build 命令不同的是，可执行文…

任何人都可以通过这三个简单的步骤编译开源代码
type: item
date: 2021-11-30T21:37:14+08:00
description: 你不需要知道如何写或读代码就可以编译它。安装软件的方法有很多，但开源让你有了一个其他地方所没有的选择：你可以自己编译代码。编译源代码的经典三步流程是：$ ./configure $ make $ sudo make install 由于有了这些命令，你可能会惊讶地发现，你不需要知道如何写代码，甚至不需要读代码就可以编译它。安装用来构建软件的命令由于这是你第一次进行编译，所以有一个一次性的准备步骤，即安装用于构建软件的命令。具体来说，你需要一个编译器。编译器（比如 GCC 或 LLVM）可以将像这样的源代码：#include <iostream> using namespace std; int main() { cout << "hello world"; } 变成 机器语言，即 CPU 用来处理信息的指令。你可以看一下机器代码，但它对你来说没有任何意义（除非你是一个 CPU）。你可以使用你的软件包管理器安装 GNU 编译器集合（GCC）和 LLVM 编译器，以及其他在 Fedora、CentOS、Mageia 和类似发行版上进行编译的基本命令：$ sudo dnf install @development clang 在 Debian、Elementary、Mint 和类似发行版上命令如下：$ sudo apt install build-essential clang 在你的系统设置好后，有几项你每次编译软件时都要重复进行的任务：下载源代码展开源代码的存档文件编译你已经有了所有你需要的命令，所以现在你需要一些软件来编译。1、下载源代码获得一个应用程序的源代码和获得任何可下载的软件一样。你得去一个网站或一个代码管理网站，如 GitLab、SourceForge 或 GitHub。 通常情况下，开源软件既有正在进行的工作（“ 当前 current ”或 “ 每夜 nightly ”）的构建版本，也有打包的“ 稳定 stable ”发布版本。在可能的情况下，使用稳定版本，除非你有理由相信，或者对代码有足够的了解，能够在出现故障时修复。术语“稳定版”表明这些代码经过了测试，而且该应用程序的程序员对代码有足够的信心，从而将其打包成 .zip 或 .tar 归档，给了它一个官方编号，有时还有一个发布名称，然后提供给一般的非程序员公众下载。在这个练习中，我使用 Angband，一个开源的（GPLv2）ASCII 地牢猎手游戏。这是一个简单的应用程序，其复杂程度刚好可以说明你在自己编译软件时需要考虑的问题。从 网站 上下载其源代码。2、展开源代码的存档文件源代码通常是以存档的形式交付的，因为源代码通常由多个文件组成的。在与之交互之前，你必须先解压，不管它是一个 tarball、一个 zip 文件、一个 7z 文件，还是其他完全不同的东西。$ tar --extract --file Angband-x.y.z.tar.gz 一旦解压缩后，就把目录切换到解压缩的目录，然后看一看。通常在目录的顶层有一个 README 文件。这个文件，一般会包含你需要做什么来编译代码的指导。README 通常包含关于代码的这些重要方面的信息：语言：代码使用的是什么语言（例如，C、C++、Rust、Python）。依赖性：你需要在你的系统上安装其他什么的软件，以便这个应用程序能够构建和运行。说明：你构建该软件所需要采取的明确步骤。偶尔，他们会在一个专门的文件中包含这些信息，这个文件被直观地称为 INSTALL。如果 README 文件中不包含这些信息，可以考虑向开发者提交一份错误报告。你不是唯一需要介绍一下源代码的人。不管他们有多么丰富的经验，每个人都会对从未见过的源代码感到陌生，而文档是很重要的！Angband 的维护者给出了在线说明的链接，描述了如何编译代码。这份文件还描述了你需要安装哪些其他软件，尽管它并没有确切地说明这一点。该网站说，“有几个不同的可选构建的前端（GCU、SDL、SDL2 和 X11），你可以使用诸如 --enable-sdl，--disable-x11 的参数配置。”这可能对你来说看起来像天书，但你经常编译代码后就会习惯。无论你是否理解 X11 或 SDL2 是什么，它们都是你经过几个月定期编译代码后经常看到的要求。你会对大多数软件需要其他软件库的想法感到适应，因为它们建立在其他技术之上。不过在这种情况下，Angband 非常灵活，无论是否有这些可选的依赖，都可以进行编译，所以现在，你可以假装没有额外的依赖。3、编译代码构建代码的典型步骤是：$ ./configure $ make $ sudo make install 这些是使用 Autotools 构建的项目的步骤，该框架是为了规范源代码的交付方式而创建的。然而，还有一些其他框架（…