2023年8月

WordPress 公司提供百年域名注册选项

WordPress 公司称,希望保存故事、照片、声音和视频等数字资产的家庭,或希望保护和记录公司历史的创始人可以使用该产品。该服务的费用为 3.8 万美元,也就是平均每年 380 美元。此方案还包括跨越多个地理区域的内容备份,带宽不受限制,以及 24/7 小时的技术支持。

消息来源:Silicon Republic
老王点评:这钱真白交,我不觉得 100 年后还会有现在的互联网。

IBM 开发出新型模拟 AI 芯片,大幅降低能耗

IBM 的研究团队近期发布了一款新型的模拟 AI 芯片设计,这种基于大脑模式的芯片设计,可以模拟人脑神经网络的工作方式,实现复杂的计算任务,同时保持高效的能源使用。其性能已经达到了 GPU 的级别,却只有同等级 GPU 1/14 的能耗。这种芯片芯片使用相变存储器(PCM),PCM 不是记录数字系统中的 0 或 1,而是非晶态和晶态之间的连续值,它可将神经网络的权重直接编码到物理芯片上。IBM 的原型芯片能编码 3500 万个 PCM 装置,单个芯片最多能支持 1700 万参数的模型。

消息来源:IBM
老王点评:模拟 AI 芯片以其更低的能耗和类似人脑的处理方式,未来有望在 AI 应用中扮演更重要的角色。

Meta 发布了编程的大语言模型

Meta 发布了 Code Llama,这是一种基于 Llama 2 的新型大型语言模型,旨在通过生成和调试代码来帮助程序员。它与 ChatGPT 和 GitHub Copilot 类似,你可以用对话要求它编写代码。Code Llama 可以用 Python、Java、C++、PHP、TypeScript、C#、Bash 脚本等多种编程语言进行编程。该模型有三个规模:7B、13B 和 43B,其中 7B 和 13B 模型速度更快,更适合需要低延迟的任务,如实时代码完成,并且可以在单个消费级 GPU 上运行。

消息来源:Ars Technica
老王点评:这样说,可以在程序员自己的笔记本上跑了?另外,我才知道 Llama 这个缩写原来也是一个英文单词,意思是美洲羊驼,你知道这种动物的。

今天我在想 —— 当你在 Linux 上运行一个简单的 “Hello World” Python 程序时,发生了什么,就像下面这个?

print("hello world")

这就是在命令行下的情况:

$ python3 hello.py
hello world

但是在幕后,实际上有更多的事情在发生。我将描述一些发生的情况,并且(更重要的是)解释一些你可以用来查看幕后情况的工具。我们将用 readelfstraceldddebugfs/procltraceddstat。我不会讨论任何只针对 Python 的部分 —— 只研究一下当你运行任何动态链接的可执行文件时发生的事情。

0、在执行 execve 之前

要启动 Python 解释器,很多步骤都需要先行完成。那么,我们究竟在运行哪一个可执行文件呢?它在何处呢?

1、解析 python3 hello.py

Shell 将 python3 hello.py 解析成一条命令和一组参数:python3['hello.py']

在此过程中,可能会进行一些如全局扩展等操作。举例来说,如果你执行 python3 *.py ,Shell 会将其扩展到 python3 hello.py

2、确认 python3 的完整路径

现在,我们了解到需要执行 python3。但是,这个二进制文件的完整路径是什么呢?解决办法是使用一个名为 PATH 的特殊环境变量。

自行验证:在你的 Shell 中执行 echo $PATH。对我来说,它的输出如下:

$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

当执行一个命令时,Shell 将会依序在 PATH 列表中的每个目录里搜索匹配的文件。

对于 fish(我的 Shell),你可以在 这里 查看路径解析的逻辑。它使用 stat 系统调用去检验是否存在文件。

自行验证:执行 strace -e stat bash,然后运行像 python3 这样的命令。你应该会看到如下输出:

stat("/usr/local/sbin/python3", 0x7ffcdd871f40) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/python3", 0x7ffcdd871f40) = -1 ENOENT (No such file or directory)
stat("/usr/sbin/python3", 0x7ffcdd871f40) = -1 ENOENT (No such file or directory)
stat("/usr/bin/python3", {st_mode=S_IFREG|0755, st_size=5479736, ...}) = 0

你可以观察到,一旦在 /usr/bin/python3 找到了二进制文件,搜索就会立即终止:它不会继续去 /sbin/bin 中查找。

对 execvp 的补充说明

如果你想要不用自己重新实现,而运行和 Shell 同样的 PATH 搜索逻辑,你可以使用 libc 函数 execvp(或其它一些函数名中含有 pexec* 函数)。

3、stat 的背后运作机制

你可能在思考,Julia,stat 到底做了什么?当你的操作系统要打开一个文件时,主要分为两个步骤:

  1. 它将 文件名 映射到一个包含该文件元数据的 inode
  2. 它利用这个 inode 来获取文件的实际内容

stat 系统调用只是返回文件的 inode 内容 —— 它并不读取任何的文件内容。好处在于这样做速度非常快。接下来让我们一起来快速了解一下 inode。(在 Dmitry Mazin 的这篇精彩文章 《磁盘就是一堆比特》中有更多的详细内容)

$ stat /usr/bin/python3
  File: /usr/bin/python3 -> python3.9
  Size: 9           Blocks: 0          IO Block: 4096   symbolic link
Device: fe01h/65025d    Inode: 6206        Links: 1
Access: (0777/lrwxrwxrwx)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2023-08-03 14:17:28.890364214 +0000
Modify: 2021-04-05 12:00:48.000000000 +0000
Change: 2021-06-22 04:22:50.936969560 +0000
 Birth: 2021-06-22 04:22:50.924969237 +0000

自行验证:我们来实际查看一下硬盘上 inode 的确切位置。

首先,我们需要找出硬盘的设备名称:

$ df
...
tmpfs             100016      604     99412   1% /run
/dev/vda1       25630792 14488736  10062712  60% /
...

看起来它是 /dev/vda1。接着,让我们寻找 /usr/bin/python3 的 inode 在我们硬盘上的确切位置(在 debugfs 提示符下输入 imap 命令):

$ sudo debugfs /dev/vda1
debugfs 1.46.2 (28-Feb-2021)
debugfs:  imap /usr/bin/python3
Inode 6206 is part of block group 0
    located at block 658, offset 0x0d00

我不清楚 debugfs 是如何确定文件名对应的 inode 的位置,但我们暂时不需要深入研究这个。

现在,我们需要计算硬盘中 “块 658,偏移量 0x0d00” 处是多少个字节,这个大的字节数组就是你的硬盘。每个块有 4096 个字节,所以我们需要到 4096 * 658 + 0x0d00 字节。使用计算器可以得到,这个值是 2698496

$ sudo dd if=/dev/vda1 bs=1 skip=2698496 count=256 2>/dev/null | hexdump -C
00000000  ff a1 00 00 09 00 00 00  f8 b6 cb 64 9a 65 d1 60  |...........d.e.`|
00000010  f0 fb 6a 60 00 00 00 00  00 00 01 00 00 00 00 00  |..j`............|
00000020  00 00 00 00 01 00 00 00  70 79 74 68 6f 6e 33 2e  |........python3.|
00000030  39 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |9...............|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000060  00 00 00 00 12 4a 95 8c  00 00 00 00 00 00 00 00  |.....J..........|
00000070  00 00 00 00 00 00 00 00  00 00 00 00 2d cb 00 00  |............-...|
00000080  20 00 bd e7 60 15 64 df  00 00 00 00 d8 84 47 d4  | ...`.d.......G.|
00000090  9a 65 d1 60 54 a4 87 dc  00 00 00 00 00 00 00 00  |.e.`T...........|
000000a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

好极了!我们找到了 inode!你可以在里面看到 python3,这是一个很好的迹象。我们并不打算深入了解所有这些,但是 Linux 内核的 ext4 inode 结构 指出,前 16 位是 “模式”,即权限。所以现在我们将看一下 ffa1 如何对应到文件权限。

  • ffa1 对应的数字是 0xa1ff,或者 41471(因为 x86 是小端表示)
  • 41471 用八进制表示就是 0120777
  • 这有些奇怪 - 那个文件的权限肯定可以是 777,但前三位是什么呢?我以前没见过这些!你可以在 inode 手册页 中找到 012 的含义(向下滚动到“文件类型和模式”)。这里有一个小的表格说 012 表示 “符号链接”。

我们查看一下这个文件,确实是一个权限为 777 的符号链接:

$ ls -l /usr/bin/python3
lrwxrwxrwx 1 root root 9 Apr  5  2021 /usr/bin/python3 -> python3.9

它确实是!耶,我们正确地解码了它。

4、准备复刻

我们尚未准备好启动 python3。首先,Shell 需要创建一个新的子进程来进行运行。在 Unix 上,新的进程启动的方式有些特殊 - 首先进程克隆自己,然后运行 execve,这会将克隆的进程替换为新的进程。

自行验证: 运行 strace -e clone bash,然后运行 python3。你应该会看到类似下面的输出:

clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f03788f1a10) = 3708100

3708100 是新进程的 PID,这是 Shell 进程的子进程。

这里有些工具可以查看进程的相关信息:

  • pstree 会展示你的系统中所有进程的树状图
  • cat /proc/PID/stat 会显示一些关于该进程的信息。你可以在 man proc 中找到这个文件的内容说明。例如,第四个字段是父进程的PID。

新进程的继承

新的进程(即将变为 python3 的)从 Shell 中继承了很多内容。例如,它继承了:

  1. 环境变量:你可以通过 cat /proc/PID/environ | tr '\0' '\n' 查看
  2. 标准输出和标准错误的文件描述符:通过 ls -l /proc/PID/fd 查看
  3. 工作目录(也就是当前目录)
  4. 命名空间和控制组(如果它在一个容器内)
  5. 运行它的用户以及群组
  6. 还有可能是我此刻未能列举出来的更多东西

5、Shell 调用 execve

现在我们准备好启动 Python 解释器了!

自行验证:运行 strace -f -e execve bash,接着运行 python3。其中的 -f 参数非常重要,因为我们想要跟踪任何可能产生的子进程。你应该可以看到如下的输出:

[pid 3708381] execve("/usr/bin/python3", ["python3"], 0x560397748300 /* 21 vars */) = 0

第一个参数是这个二进制文件,而第二个参数是命令行参数列表。这些命令行参数被放置在程序内存的特定位置,以便在运行时可以访问。

那么,execve 内部到底发生了什么呢?

6、获取该二进制文件的内容

我们首先需要打开 python3 的二进制文件并读取其内容。直到目前为止,我们只使用了 stat 系统调用来获取其元数据,但现在我们需要获取它的内容。

让我们再次查看 stat 的输出:

$ stat /usr/bin/python3
  File: /usr/bin/python3 -> python3.9
  Size: 9           Blocks: 0          IO Block: 4096   symbolic link
Device: fe01h/65025d    Inode: 6206        Links: 1
...

该文件在磁盘上占用 0 个块的空间。这是因为符号链接(python3.9)的内容实际上是存储在 inode 自身中:在下面显示你可以看到(来自上述 inode 的二进制内容,以 hexdump 格式分为两行输出)。

00000020  00 00 00 00 01 00 00 00  70 79 74 68 6f 6e 33 2e  |........python3.|
00000030  39 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |9...............|

因此,我们将需要打开 /usr/bin/python3.9 。所有这些操作都在内核内部进行,所以你并不会看到其他的系统调用。

每个文件都由硬盘上的一系列的 构成。我知道我系统中的每个块是 4096 字节,所以一个文件的最小大小是 4096 字节 —— 甚至如果文件只有 5 字节,它在磁盘上仍然占用 4KB。

自行验证:我们可以通过 debugfs 找到块号,如下所示:(再次说明,我从 Dmitry Mazin 的《磁盘就是一堆比特》文章中得知这些步骤)。

$ debugfs /dev/vda1
debugfs:  blocks /usr/bin/python3.9
145408 145409 145410 145411 145412 145413 145414 145415 145416 145417 145418 145419 145420 145421 145422 145423 145424 145425 145426 145427 145428 145429 145430 145431 145432 145433 145434 145435 145436 145437

接下来,我们可以使用 dd 来读取文件的第一个块。我们将块大小设定为 4096 字节,跳过 145408 个块,然后读取 1 个块。

$ dd if=/dev/vda1 bs=4096 skip=145408 count=1 2>/dev/null | hexdump -C | head
00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 3e 00 01 00 00 00  c0 a5 5e 00 00 00 00 00  |..>.......^.....|
00000020  40 00 00 00 00 00 00 00  b8 95 53 00 00 00 00 00  |@.........S.....|
00000030  00 00 00 00 40 00 38 00  0b 00 40 00 1e 00 1d 00  |[email protected]...@.....|
00000040  06 00 00 00 04 00 00 00  40 00 00 00 00 00 00 00  |........@.......|
00000050  40 00 40 00 00 00 00 00  40 00 40 00 00 00 00 00  |@.@.....@.@.....|
00000060  68 02 00 00 00 00 00 00  68 02 00 00 00 00 00 00  |h.......h.......|
00000070  08 00 00 00 00 00 00 00  03 00 00 00 04 00 00 00  |................|
00000080  a8 02 00 00 00 00 00 00  a8 02 40 00 00 00 00 00  |..........@.....|
00000090  a8 02 40 00 00 00 00 00  1c 00 00 00 00 00 00 00  |..@.............|

你会发现,这样我们得到的输出结果与直接使用 cat 读取文件所获得的结果完全一致。

$ cat /usr/bin/python3.9 | hexdump -C | head
00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 3e 00 01 00 00 00  c0 a5 5e 00 00 00 00 00  |..>.......^.....|
00000020  40 00 00 00 00 00 00 00  b8 95 53 00 00 00 00 00  |@.........S.....|
00000030  00 00 00 00 40 00 38 00  0b 00 40 00 1e 00 1d 00  |[email protected]...@.....|
00000040  06 00 00 00 04 00 00 00  40 00 00 00 00 00 00 00  |........@.......|
00000050  40 00 40 00 00 00 00 00  40 00 40 00 00 00 00 00  |@.@.....@.@.....|
00000060  68 02 00 00 00 00 00 00  68 02 00 00 00 00 00 00  |h.......h.......|
00000070  08 00 00 00 00 00 00 00  03 00 00 00 04 00 00 00  |................|
00000080  a8 02 00 00 00 00 00 00  a8 02 40 00 00 00 00 00  |..........@.....|
00000090  a8 02 40 00 00 00 00 00  1c 00 00 00 00 00 00 00  |..@.............|

关于魔术数字的额外说明

这个文件以 ELF 开头,这是一个被称为“ 魔术数字 magic number ”的标识符,它是一种字节序列,告诉我们这是一个 ELF 文件。在 Linux 上,ELF 是二进制文件的格式。

不同的文件格式有不同的魔术数字。例如,gzip 的魔数是 1f8b。文件开头的魔术数字就是 file blah.gz 如何识别出它是一个 gzip 文件的方式。

我认为 file 命令使用了各种启发式方法来确定文件的类型,而其中,魔术数字是一个重要的特征。

7、寻找解释器

我们来解析这个 ELF 文件,看看里面都有什么内容。

自行验证:运行 readelf -a /usr/bin/python3.9。我得到的结果是这样的(但是我删减了大量的内容):

$ readelf -a /usr/bin/python3.9
ELF Header:
    Class:                             ELF64
    Machine:                           Advanced Micro Devices X86-64
...
->  Entry point address:               0x5ea5c0
...
Program Headers:
  Type           Offset             VirtAddr           PhysAddr
  INTERP         0x00000000000002a8 0x00000000004002a8 0x00000000004002a8
                 0x000000000000001c 0x000000000000001c  R      0x1
->      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
        ...
->        1238: 00000000005ea5c0    43 FUNC    GLOBAL DEFAULT   13 _start

从这段内容中,我理解到:

  1. 请求内核运行 /lib64/ld-linux-x86-64.so.2 来启动这个程序。这就是所谓的动态链接器,我们将在随后的部分对其进行讨论。
  2. 该程序制定了一个入口点(位于 0x5ea5c0),那里是这个程序代码开始的地方。

接下来,让我们一起来聊聊动态链接器。

8、动态链接

好的!我们已从磁盘读取了字节数据,并启动了这个“解释器”。那么,接下来会发生什么呢?如果你执行 strace -o out.strace python3,你会在 execve 系统调用之后观察到一系列的信息:

execve("/usr/bin/python3", ["python3"], 0x560af13472f0 /* 21 vars */) = 0
brk(NULL)                       = 0xfcc000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=32091, ...}) = 0
mmap(NULL, 32091, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f718a1e3000
close(3)                        = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 l\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=149520, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f718a1e1000
...
close(3)                        = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3

这些内容初看可能让人望而生畏,但我希望你能重点关注这一部分:openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libpthread.so.0" ...。这里正在打开一个被称为 pthread 的 C 语言线程库,运行 Python 解释器时需要这个库。

自行验证:如果你想知道一个二进制文件在运行时需要加载哪些库,你可以使用 ldd 命令。下面展示的是我运行后的效果:

$ ldd /usr/bin/python3.9
    linux-vdso.so.1 (0x00007ffc2aad7000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f2fd6554000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f2fd654e000)
    libutil.so.1 => /lib/x86_64-linux-gnu/libutil.so.1 (0x00007f2fd6549000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f2fd6405000)
    libexpat.so.1 => /lib/x86_64-linux-gnu/libexpat.so.1 (0x00007f2fd63d6000)
    libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f2fd63b9000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2fd61e3000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f2fd6580000)

你可以看到,第一个列出的库就是 /lib/x86_64-linux-gnu/libpthread.so.0,这就是它被第一个加载的原因。

关于 LD\_LIBRARY\_PATH

说实话,我关于动态链接的理解还有些模糊,以下是我所了解的一些内容:

  • 动态链接发生在用户空间,我的系统上的动态链接器位于 /lib64/ld-linux-x86-64.so.2. 如果你缺少动态链接器,可能会遇到一些奇怪的问题,比如这种 奇怪的“文件未找到”错误
  • 动态链接器使用 LD_LIBRARY_PATH 环境变量来查找库
  • 动态链接器也会使用 LD_PRELOAD 环境变量来覆盖你想要的任何动态链接函数(你可以使用它来进行 有趣的魔改,或者使用像 jemalloc 这样的替代品来替换默认内存分配器)
  • strace 的输出中有一些 mprotect,因为安全原因将库代码标记为只读
  • 在 Mac 上,不是使用 LD_LIBRARY_PATH(Linux),而是 DYLD_LIBRARY_PATH

你可能会有疑问,如果动态链接发生在用户空间,我们为什么没有看到大量的 stat 系统调用在 LD_LIBRARY_PATH 中搜索这些库,就像 Bash 在 PATH 中搜索那样?

这是因为 ld/etc/ld.so.cache 中有一个缓存,因此所有之前已经找到的库都会被记录在这里。你可以在 strace 的输出中看到它正在打开缓存 - openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3

完整的 strace 输出 中,我仍然对动态链接之后出现的一些系统调用感到困惑(什么是 prlimit64?本地环境的内容是如何介入的?gconv-modules.cache 是什么?rt_sigaction 做了什么?arch_prctl 是什么?以及 set_tid_addressset_robust_list 是什么?)。尽管如此,我觉得已经有了一个不错的开头。

旁注:ldd 实际上是一个简单的 Shell 脚本!

在 Mastodon 上,有人 指出ldd 实际上是一个 Shell 脚本,它设置了 LD_TRACE_LOADED_OBJECTS=1 环境变量,然后启动程序。因此,你也可以通过以下方式实现相同的功能:

$ LD_TRACE_LOADED_OBJECTS=1 python3
    linux-vdso.so.1 (0x00007ffe13b0a000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f01a5a47000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f01a5a41000)
    libutil.so.1 => /lib/x86_64-linux-gnu/libutil.so.1 (0x00007f2fd6549000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f2fd6405000)
    libexpat.so.1 => /lib/x86_64-linux-gnu/libexpat.so.1 (0x00007f2fd63d6000)
    libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f2fd63b9000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2fd61e3000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f2fd6580000)

事实上,ld 也是一个可以直接运行的二进制文件,所以你也可以通过 /lib64/ld-linux-x86-64.so.2 --list /usr/bin/python3.9 来达到相同的效果。

关于 init 和 fini

让我们来谈谈这行 strace 输出中的内容:

set_tid_address(0x7f58880dca10) = 3709103

这似乎与线程有关,我认为这可能是因为 pthread 库(以及所有其他动态加载的库)在加载时得以运行初始化代码。在库加载时运行的代码位于 init 区域(或者也可能是 .ctors 区域)。

自行验证:让我们使用 readelf 来看看这个:

$ readelf -a /lib/x86_64-linux-gnu/libpthread.so.0
...
  [10] .rela.plt         RELA             00000000000051f0  000051f0
       00000000000007f8  0000000000000018  AI       4    26     8
  [11] .init             PROGBITS         0000000000006000  00006000
       000000000000000e  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         0000000000006010  00006010
       0000000000000560  0000000000000010  AX       0     0     16
...

这个库没有 .ctors 区域,只有一个 .init。但是,.init 区域都有些什么内容呢?我们可以使用 objdump 来反汇编这段代码:

$ objdump -d /lib/x86_64-linux-gnu/libpthread.so.0
Disassembly of section .init:

0000000000006000 <_init>:
    6000:       48 83 ec 08             sub    $0x8,%rsp
    6004:       e8 57 08 00 00          callq  6860 <__pthread_initialize_minimal>
    6009:       48 83 c4 08             add    $0x8,%rsp
    600d:       c3

所以它在调用 __pthread_initialize_minimal。我在 glibc 中找到了 这个函数的代码,尽管我不得不找到一个较早版本的 glibc,因为在更近的版本中,libpthread 不再是一个独立的库

我不确定这个 set_tid_address 系统调用是否实际上来自 __pthread_initialize_minimal,但至少我们知道了库可以通过 .init 区域在启动时运行代码。

这里有一份关于 .init 区域的 elf 手册的笔记:

$ man elf

.init 这个区域保存着对进程初始化代码有贡献的可执行指令。当程序开始运行时,系统会安排在调用主程序入口点之前执行该区域中的代码。

在 ELF 文件中也有一个在结束时运行的 .fini 区域,以及其他可以存在的区域 .ctors / .dtors(构造器和析构器)。

好的,关于动态链接就说这么多。

9、转到 \_start

在动态链接完成后,我们进入到 Python 解释器中的 _start。然后,它将执行所有正常的 Python 解析器会做的事情。

我不打算深入讨论这个,因为我在这里关心的是关于如何在 Linux 上运行二进制文件的一般性知识,而不是特别针对 Python 解释器。

10、写入字符串

不过,我们仍然需要打印出 “hello world”。在底层,Python 的 print 函数调用了 libc 中的某个函数。但是,它调用了哪一个呢?让我们来找出答案!

自行验证:运行 ltrace -o out python3 hello.py

$ ltrace -o out python3 hello.py
$ grep hello out
write(1, "hello world\n", 12) = 12

看起来它确实在调用 write 函数。

我必须承认,我对 ltrace 总是有一些疑虑 —— 与我深信不疑的 strace 不同,我总是不完全确定 ltrace 是否准确地报告了库调用。但在这个情况下,它似乎有效。并且,如果我们查阅 cpython 的源代码,它似乎在一些地方确实调用了 write() 函数,所以我倾向于相信这个结果。

什么是 libc?

我们刚刚提到,Python 调用了 libc 中的 write 函数。那么,libc 是什么呢?它是 C 的标准库,负责许多基本操作,例如:

  • malloc 分配内存
  • 文件 I/O(打开/关闭文件)
  • 执行程序(像我们之前提到的 execvp
  • 使用 getaddrinfo 查找 DNS 记录
  • 使用 pthread 管理线程

在 Linux 上,程序不一定需要使用 libc(例如 Go 就广为人知地未使用它,而是直接调用了 Linux 系统调用),但是我常用的大多数其他编程语言(如 node、Python、Ruby、Rust)都使用了 libc。我不确定 Java 是否也使用了。

你能通过在你的二进制文件上执行 ldd 命令,检查你是否正在使用 libc:如果你看到了 libc.so.6 这样的信息,那么你就在使用 libc。

为什么 libc 重要?

你也许在思考 —— 为何重要的是 Python 调用 libc 的 write 函数,然后 libc 再调用 write 系统调用?为何我要着重提及 libc 是调用过程的一环?

我认为,在这个案例中,这并不真的很重要(根据我所知,libc 的 write 函数与 write 系统调用的映射相当直接)。

然而,存在不同的 libc 实现,有时它们的行为会有所不同。两个主要的实现是 glibc(GNU libc)和 musl libc。

例如,直到最近,musl 的 getaddrinfo 并不支持 TCP DNS这是一篇关于这个问题引发的错误的博客文章

关于 stdout 和终端的小插曲

在我们的程序中,stdout(1 文件描述符)是一个终端。你可以在终端上做一些有趣的事情!例如:

  1. 在终端中运行 ls -l /proc/self/fd/1。我得到了 /dev/pts/2 的结果。
  2. 在另一个终端窗口中,运行 echo hello > /dev/pts/2
  3. 返回到原始终端窗口。你应会看到 hello 被打印出来了!

暂时就到这儿吧!

希望通过上文,你对 hello world 是如何打印出来的有了更深的了解!我暂时不再添加更多的细节,因为这篇文章已经足够长了,但显然还有更多的细节可以探讨,如果大家能提供更多的细节,我可能会添加更多的内容。如果你有关于我在这里没提到的程序内部调用过程的任何工具推荐,我会特别高兴。

我很期待看到一份 Mac 版的解析

我对 Mac OS 的一个懊恼是,我不知道如何在这个级别上解读我的系统——当我打印 “hello world”,我无法像在 Linux 上那样,窥视背后的运作机制。我很希望看到一个深度的解析。

我所知道的一些在 Mac 下的对应工具:

  • ldd -> otool -L
  • readelf -> otool
  • 有人说你可以在 Mac 上使用 dtrussdtrace 来代替 strace,但我尚未有足够的勇气关闭系统完整性保护来让它工作。
  • strace -> sc_usage 似乎能够收集关于系统调用使用情况的统计信息,fs_usage 则可以收集文件使用情况的信息。

延伸阅读

一些附加的链接:

(题图:MJ/b87ed0a2-80d6-49cd-b2bf-1ef822485e3f)


via: https://jvns.ca/blog/2023/08/03/behind--hello-world/

作者:Julia Evans 选题:lujun9972 译者:ChatGPT 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

埃隆·马斯克的 Twitter/X 关注者水分严重

埃隆·马斯克是 Twitter/X 上关注者最多的用户,高达 1.53 亿。有研究对他的关注者进行了分析,发现其中,

  • 42%(约 6530 万)的用户没有任何关注者;
  • 72%(1.12 亿)的用户其关注数量不足 10 人,全部 1.53 亿用户的关注者数量的中位数仅为 1 个;
  • 超过 40%(6250 万)的关注者没有发过任何推文或删除了全部推文,超过 1 亿的关注者发表的推文不足 10 条;
  • 超过 25%(3890 万)的关注者是在他收购 Twitter 后才创建的账号;
  • 而他的关注者中仅仅只有 0.3%(45 万)的用户是每月 8 美元的付费订阅用户(付费订阅用户总数据估计约为 83 万)。
消息来源:Mashable
老王点评:如果说,马斯克给自己弄了很多假粉,那这是自己演给自己玩吗?

OpenTF 宣布创建 Terraform 分支

HashiCorp 公司创建的基础设施即代码软件 Terraform 最初于 2014 年在 MPL 2.0 许可证下开源。但在 8 月 10 日,HashiCorp 突然将 Terraform 的许可证从 MPL 切换到了非开源的商业源代码许可证(BSL)。为了保持 Terraform 的开源性,Terraform 社区发布了 OpenTF 宣言,宣布创建 Terraform 的分支,并成立了 OpenTF 基金会。OpenTF 基金会表示,已经有四家公司承诺为该项目提供 14 名全职工程师,并预计未来几周将至少增加一倍。而且,它指出过去两年 HashiCorp 公司只提供了大约 5 名全职工程师去维护 Terraform。

消息来源:OpenTF
老王点评:当企业需要时它开源,当企业不满时它闭源。当开源时社区来了,当闭源时社区分叉了。这样的戏码我们已经见过很多次了,难道这是企业开源的宿命吗?

调查发现业界对 Rust 应用的担忧减少了 21%

Rust 项目连续第六年对 Rust 编程语言进行了调查,共有 9,433 人完成了调查。根据调查数据,

  • 超过 90% 的调查对象认为自己是 Rust 用户,其中 47% 的人每天都在使用 Rust;
  • 27% 的受访者可以编写可投入生产的代码;
  • 在放弃 Rust 用户中,30% 认为困难是放弃的主要原因;
  • 29.7% 的受访者表示,他们在工作场所的大部分编码工作都使用 Rust,这比上一年增加了 51.8%;
  • 使用 Rust 的最主要原因包括编写无错误软件的能力(86%)、Rust 的性能特点(84%)以及 Rust 的安全保障(69%);
  • 39% 的受访者表示学习过程具有挑战性,9% 的受访者表示在工作中采用 Rust 拖慢了他们团队的速度;不过,60% 的生产型用户认为,总体而言,采用 Rust 的成本是值得的;
  • 26% 的人担心 Rust 背后的开发者和维护者得不到适当的支持,这比去年的调查结果减少了 30% 以上。
消息来源:Rust
老王点评:Rust 是颇受追捧,但是我其实认为 Rust 的最大风险可能是项目方瞎折腾。

网络 绑定 Bonding 成组 Teaming ) 是 Linux 的一项内核特性,它让我们可以将多个网络接口(例如 ens192ens224)聚合为一个专有的虚拟网络接口,被称为通道绑定(bond0)。这样做能够提升吞吐量并增加冗余备份。

网络绑定一共支持 7 种模式,你可以根据实际需求进行设置。 链接聚合控制协议 Link Aggregation Control Protocol (LACP), 即模式 4(802.3ad)因其支持链接聚合与冗余而被广泛应用。

在本篇文章中,我们将引导你学习如何在 RHEL 系统中配置网卡(网络)绑定。

LACP 绑定的前置条件

  • 网络团队需要在网络交换机的端口上开启 LACP(802.3ad)来实现链接的聚合。
  • 一个 Linux 系统应该配备至少两个网络接口。
  • 对于物理服务器,我们推荐在板载接口与 PCI 接口间进行绑定配置,以避免在主机端的网络卡出现单点故障。

Bonding 模块

你可以使用 lsmod 命令来确认你的 Linux 系统是否已经加载了 bonding 模块。

lsmod | grep -i bonding
bonding               12451  0

系统应该默认已加载。如果未看到,可以运用 modprobe 命令进行加载。

modprobe bonding

创建绑定接口

/etc/sysconfig/network-scripts/ 路径下,创建一个名为 ifcfg-bond0 的绑定接口文件。依据你的网络情况,你可能需要修改诸如 IPMASK 以及 GATEWAY 等值。

vi /etc/sysconfig/network-scripts/ifcfg-bond0
TYPE=Bond
DEVICE=bond0
NAME=bond0
BONDING_MASTER=yes
BOOTPROTO=none
ONBOOT=yes
IPADDR=192.168.1.100
NETMASK=255.255.255.0
GATEWAY=192.168.1.1
BONDING_OPTS="mode=4 miimon=100 lacp_rate=1"
参数描述
BONDING_MASTER=yes表示设备是一个绑定主设备。
mode=4绑定模式是 IEEE 802.3ad 动态链接聚合(LACP)。
miimon=100定义以毫秒单位的 MII 链路监测频率,这决定了多久检查每个从属链路的状态一次以寻找失败的链路。0 值将禁用 MII 链路监测。100 是个不错的初始值。
lacp_rate=1一个设置项,规定我们将以何种频率要求我们的链路伙伴每秒钟发送 LACPDU。默认为慢,即 0

配置第一个子接口

修改你希望添加到绑定中的第一个子接口。请根据你的实际环境使用合适的接口名。

vi /etc/sysconfig/network-scripts/ifcfg-ens192
TYPE=Ethernet
BOOTPROTO=none
DEVICE=ens192
ONBOOT=yes
MASTER=bond0
SLAVE=yes

配置第二个子接口

修改你希望添加到绑定中的第二个子接口。请根据你的实际环境使用合适的接口名。

vi /etc/sysconfig/network-scripts/ifcfg-ens224
TYPE=Ethernet
BOOTPROTO=none
DEVICE=ens224
ONBOOT=yes
MASTER=bond0
SLAVE=yes

重启网络服务

重启网络服务以激活绑定接口。

systemctl restart network

验证绑定配置

你可以借助 ip 命令 来查看绑定接口以及其子接口的情况。可以看到,bond0 现在已启动并在运行。

查阅绑定接口状态

检查以下文件,你可以看到绑定接口及其子接口的详细信息。输出结果应该看起来很不错,我们能看到诸如绑定模式,MII 状态,MII 轮询间隔,LACP 速率,端口数量等信息。

cat /proc/net/bonding/bond0
Ethernet Channel Bonding Driver: v3.7.1 (April 27, 2011)

Bonding Mode: IEEE 802.3ad Dynamic link aggregation
Transmit Hash Policy: layer (0)
MII Status: up
MII Polling Interval (ms): 100
Up Delay (ms): 0
Down Delay (ms): 0

802.3ad info
LACP rate: fast
Min links: 0
Aggregator selection policy (ad_select): stable
System priority: 65535
System MAC address: c8:5b:76:4d:d4:5c
Active Aggregator Info:
        Aggregator ID: 1
        Number of ports: 2
        Actor Key: 15
        Partner Key: 32773
        Partner Mac Address: e4:a7:a0:32:fc:e9

Slave Interface: ens192
MII Status: up
Speed: 10000 Mbps
Duplex: full
Link Failure Count: 0
Permanent HW addr: c8:5b:76:4d:d4:5c
Slave queue ID: 0
Aggregator ID: 1
Actor Churn State: none
Partner Churn State: none
Actor Churned State: 0
Partner Churned State: 0
details actor lacp pdu:
    system priority: 65535
    system mac address: c8:5b:76:4d:d4:5c
    port key: 15
    port priority: 255
    port number: 1
    port state: 63
details Partner lacp pdu:
    system priority: 32667
    system mac address: e4:a7:a0:32:fc:e9
    oper key: 32773
    port priority: 32768
    port number: 290
    port state: 61

Slave Interface: ens224
MII Status: up
Speed: 10000 Mbps
Duplex: full
Link Failure Count: 0
Permanent HW addr: e4:a7:a0:32:fc:e9
Slave queue ID: 0
Aggregator ID: 1
Actor Churn State: none
Partner Churn State: none
Actor Churned State: 0
Partner Churned State: 0
details actor lacp pdu:
    system priority: 65535
    system mac address: e4:a7:a0:32:fc:e9
    port key: 15
    port priority: 255
    port number: 2
    port state: 63
details Partner lacp pdu:
    system priority: 32667
    system mac address: c8:5b:76:4d:d4:5c
    oper key: 32773
    port priority: 32768
    port number: 16674
    port state: 61

容错/冗余测试

为了验证容错性和连接速度,你可以逐个断开接口,然后检查服务器是否仍旧可达。

  • 测试用例-1:当两个子接口都启动并运行时,使用 ethtool 命令 检查链路速度。
  • 测试用例-2:断开第一个子接口,然后尝试访问系统。
  • 测试用例-3:断开第二个子接口,然后尝试访问系统。

测试用例-1:

如果你想检测下连接速度:没错,我在 bond0 上看到了 20 Gbps 的速度,因为每个子接口支持 10 Gbps。

ethtool bond0
Settings for bond0:
        Supported ports: [ ]
        Supported link modes:   Not reported
        Supported pause frame use: No
        Supports auto-negotiation: No
        Supported FEC modes: Not reported
        Advertised link modes:  Not reported
        Advertised pause frame use: No
        Advertised auto-negotiation: No
        Advertised FEC modes: Not reported
        Speed: 20000Mb/s
        Duplex: Full
        Port: Other
        PHYAD: 0
        Transceiver: internal
        Auto-negotiation: off
        Link detected: yes

测试用例-2:

现在我们将关闭第一个子接口。

ifdown ens192
Device 'ens192' successfully disconnected.

通过 ssh 尝试访问系统。没问题,系统现在是可以访问的。

ssh [email protected]

由于已经有一个子接口关闭,你现在在 bond0 上只能看到 10 Gbps 的速度。

ethtool bond0 | grep -i speed
            Speed: 10000Mb/s

现在,我们再次查看绑定接口的状态。可以看出,只有一个子接口处于活跃状态。

cat /proc/net/bonding/bond0

测试用例-3:

我们来关闭第二个子接口,并进行类似测试用例-2 的测试:

ifdown ens224
Device 'ens224' successfully disconnected.

结语

我希望你已经掌握了在 RHEL 上配置 LACP 绑定的方法。

在本教程中,我们为你展示了在 RHEL 系统配置网络绑定或网卡聚合的最简单方式。

如果你有任何疑问或者反馈,欢迎在下面留言。

(题图:MJ/939f6ba6-eb46-480d-8879-3a422c7425d2)


via: https://www.2daygeek.com/configure-network-bonding-nic-teaming-rhel/

作者:Jayabal Thiyagarajan 选题:lujun9972 译者:ChatGPT 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

最新发行的 Kali Linux 版本,注重于底层技术更迭和部分全新工具的推出。

作为全球渗透测试人员的优选,Kali Linux 作为 注重于渗透测试的 发行版,为各种使用情景提供了丰富的工具库。

就在 几个月之前,我们介绍过 Kali Linux 2023.2 版本,该版本中包含一系列显著改进。

现如今,我们迎来了 Kali Linux 2023.3 的更新发布,这一版本在更大程度上 关注了后端技术

让我们详细探索此次更新中的新亮点。

? Kali Linux 2023.3:有哪些新鲜事?

借助 Linux 内核 6.3,Kali Linux 2023.3 版本作为一次高度专注的发布,聚焦于几项关键更新。主要亮点包括:

  • 全新的打包工具
  • 内部基础设施优化
  • 新增加网络工具

全新的打包工具

Kali Linux 已经添加了新功能、各项生活质量改良以及新脚本,全面扩充了他们开放的 自制脚本库

打包工具中包含:

内部基础设施优化

得益于最近发布的 Debian 12,Kali Linux 的开发者得以全面改造、重新设计自己的基础设施。

这是一项宏大的工程,通过采用单一的软件处理 Kali Linux 的重要组成部分,使得他们得以简化自己的软件栈

因此,以 Debian 12 为基础,使用 Cloudflare 作为 CDN / WAF,Web 服务器服务采用 Nginx,并且选用了 Ansible 作为 基础设施即代码 Infrastructure as Code 的工具。

更多的优化将会在即将推出的更新中进行。

新增网络工具

伴随着 Linux 内核的升级至 6.3.7,Kali Linux 2023.3 版本 推出了一系列专门针对网络操作的新工具,这些新工具已经在网络仓库中对用户开放。

新工具包括:

  • kustomize - Kubernetes YAML 配置的定制工具。
  • Rekono - 完整的渗透测试过程自动化平台。
  • rz-ghidra - 用于 rizin 的深度 ghidra 反编译器以及 sleigh 反汇编器的集成工具。
  • Calico - 云原生网络和网络安全解决方案。
  • ImHex - 适应各种使用场景的 Hex 编辑器。
  • Villain - 可以处理多个反向 Shell 的 C2 框架。

?️ 其它变动与提升

除了以上提及的要点,还有一些显著的变动值得注意:

  • 两个包 king-phisherplecost 已经舍弃,转而选择其他代替方案。
  • 当在增强会话模式下使用 Hyper-V 时,引入了 PipeWire 的支持。
  • 对 Kali Purple 进行了多项改进。

你可以阅读 官方发布说明 以获取这次版本更新的更细致信息。

? 获取 Kali Linux 2023.3

你可以在官方网站获取 Kali Linux 的最新发布版本。

Kali Linux 2023.3

对于现有用户,你可以运行以下命令进行更新到最新版本:

sudo apt update && sudo apt full-upgrade

? 你是否打算在新版本中尝试新的工具?欢迎在评论区中分享你的想法。

(题图:MJ/6539fac3-8d3d-47b8-8566-a9ce376b53d3)


via: https://news.itsfoss.com/kali-linux-2023-3-release/

作者:Sourav Rudra 选题:lujun9972 译者:译者ID 校对:校对者ID

本文由 LCTT 原创编译,Linux中国 荣誉推出

Amazon Linux 2023 仍然没有交付虚拟机镜像

Amazon Linux 2023 于 3 月 15 日发布时,本应以虚拟机镜像的形式提供,让企业可以在自己的服务器上运行。这是 Amazon 曾经承诺过的,但后来它删除了这一承诺,并且没有给出任何理由。从去年发布的功能请求开始,到一年后的类似请求,在过去的 18 个月里,用户一直希望 Amazon 澄清该情况,但没有任何解释。上周,Amazon 标记了该功能请求,并将其添加到 Amazon Linux 2023 功能积压列表中,但也没有给出具体的承诺或时间表。

消息来源:The Register
老王点评:就这样的 Amazon Linux,怎么和其它的企业级 Linux 竞争啊?是已经躺平了么?

科学家发现让人不睡觉的机制

华盛顿州立大学的研究人员发表论文,他们使用遗传和化学技术可逆的改变小鼠基底前脑中星形胶质细胞的活性,能让小鼠连续几个小时保持清醒,没有表现出任何睡意的迹象。研究人员表示,这项研究或有助于创造出能长时间让人保持清醒的药物,比如轮班工人不会昏昏欲睡,宇航员、飞行员、士兵、医疗人员、急救人员可以在较长时间内无需睡眠。

消息来源:medicalxpress
老王点评:我看到这一条时,第一反应就是程序员们可以 7X24 了……

Chrome 无用的阅读模式将有朗读功能

谷歌正在积极为其 Chrome 浏览器的阅读模式增加新的功能,在 Canary 频道开始测试朗读选项。该功能可以将整篇文章朗读出来,就像你在听有声读物一样。一些主流浏览器和应用程序(如 Pocket)已经具备了这项功能,但谷歌 Chrome 浏览器现在才开始测试,并且,语音输出听起来非常机械,就像早期的 TTS 转换一样。这对于拥有一些听起来最自然的语音模型的谷歌来说尤其讽刺。

消息来源:Android Police
老王点评:最近,微信公众号也开始有朗读模式了,但是似乎很快就没啥人用了。