0%

本文的目的是通过计算一个 ELF 文件各部分的大小来认识 ELF 文件的组成。

基础知识

链接器的两种视角

ELF 文件的主要部分,

  • 从链接器的视角来看由section组成,每个section的信息记录在 ELF 文件尾部的section header table部分
  • 从加载器的视角(即执行视角)来看由segment组成,每个segment的信息记录在 ELF 文件头部的program header table部分

ELF 文件大小计算

1
2
3
4
int main()
{
return 0;
}

使用gcc -o test test.c编译上面的 c 文件,并用ls -l查看文件的大小(在作者的电脑上是 15832 字节)

使用readelf -h test可得知 ELF header 的相关信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 (Position-Independent Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x1040
Start of program headers: 64 (bytes into file)
Start of section headers: 13912 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 13
Size of section headers: 64 (bytes)
Number of section headers: 30
Section header string table index: 29

本文只关注大小,由Size of this header可以得知ELF header的大小是 64 字节

program header table 应该紧随 ELF header 之后,那么其相对于 ELF 文件起始处的偏移应该也是 64 字节,Start of program headers一项可以验证此结论

program header table 里存放了多条 program header,每条 program header 都存放着每个segment的元数据。在本例中,由Size of program headersNumber of program headers可知 program header table 一共有 13 条,每一条的大小是 56 字节。即整个表的大小为 13 * 56 = 728 字节。

sections部分,或segments部分,应当紧随 program header table 之后,所以推测其起始偏移应该是 64 + 728 = 792 字节处

使用readelf -S -W test命令,获得如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 0000000000000318 000318 00001c 00 A 0 0 1
[ 2] .note.gnu.property NOTE 0000000000000338 000338 000020 00 A 0 0 8
[ 3] .note.gnu.build-id NOTE 0000000000000358 000358 000024 00 A 0 0 4
[ 4] .note.ABI-tag NOTE 000000000000037c 00037c 000020 00 A 0 0 4
[ 5] .gnu.hash GNU_HASH 00000000000003a0 0003a0 000024 00 A 6 0 8
[ 6] .dynsym DYNSYM 00000000000003c8 0003c8 000090 18 A 7 1 8
[ 7] .dynstr STRTAB 0000000000000458 000458 000088 00 A 0 0 1
[ 8] .gnu.version VERSYM 00000000000004e0 0004e0 00000c 02 A 6 0 2
[ 9] .gnu.version_r VERNEED 00000000000004f0 0004f0 000030 00 A 7 1 8
[10] .rela.dyn RELA 0000000000000520 000520 0000c0 18 A 6 0 8
[11] .init PROGBITS 0000000000001000 001000 000017 00 AX 0 0 4
[12] .plt PROGBITS 0000000000001020 001020 000010 10 AX 0 0 16
[13] .plt.got PROGBITS 0000000000001030 001030 000008 08 AX 0 0 8
[14] .text PROGBITS 0000000000001040 001040 0000f4 00 AX 0 0 16
[15] .fini PROGBITS 0000000000001134 001134 000009 00 AX 0 0 4
[16] .rodata PROGBITS 0000000000002000 002000 000004 04 AM 0 0 4
[17] .eh_frame_hdr PROGBITS 0000000000002004 002004 00002c 00 A 0 0 4
[18] .eh_frame PROGBITS 0000000000002030 002030 0000ac 00 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000003e00 002e00 000008 08 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000003e08 002e08 000008 08 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000003e10 002e10 0001b0 10 WA 7 0 8
[22] .got PROGBITS 0000000000003fc0 002fc0 000028 08 WA 0 0 8
[23] .got.plt PROGBITS 0000000000003fe8 002fe8 000018 08 WA 0 0 8
[24] .data PROGBITS 0000000000004000 003000 000010 00 WA 0 0 8
[25] .bss NOBITS 0000000000004010 003010 000008 00 WA 0 0 1
[26] .comment PROGBITS 0000000000000000 003010 00001f 01 MS 0 0 1
[27] .symtab SYMTAB 0000000000000000 003030 000348 18 28 18 8
[28] .strtab STRTAB 0000000000000000 003378 0001c9 00 0 0 1
[29] .shstrtab STRTAB 0000000000000000 003541 000110 00 0 0 1

重点看 Off(set) 与 Size 两列。offset 是该 section 相对于整个 ELF 文件起始的偏移,第一个有实质内容的 section 是.interp,其起始偏移为 0x318,正好对应前文的 792 字节。

随后,看最后的 section .shstrtab,其起始偏移为 0x3541,大小为 0x110,即所有 sections 的结束偏移为 0x3541 + 0x110 = 0x3651,即 13905 字节。

section header table 应当紧随 sections 之后,即其起始偏移为 13905 字节。由Number of section headersSize of section headers可知,表中有 30 条 section header,每条的大小是 64 字节,所以表的大小是 30 * 64 = 1920 字节。那么整个文件的大小为 13905 + 1920 = 15825 字节。

我们验证一下

1
2
$ du -b test
15832 test

咦,实际的大小是 15832 字节。问题在于 ELF 文件的每一部分都是 8 字节对齐的(TODO:在哪定的?),所以,虽然 sections 的结束偏移为 13905 字节,但 section header table 应当对齐 8 字节,即应当从 13912 偏移处开始,从 ELF header 中的Start of section headers可以验证此结论。

TODO 挺麻烦的
修改并搭建了 ros 的编译农场位于 https://j.z572.online
ros_buildfarm 仓库位于 https://gitee.com/Z572/ros_buildfarm
rosdistro 仓库位于 https://gitee.com/Z572/rosdistro
buildfarm_config 仓库位于 https://gitee.com/Z572/ros_buildfarm_config
https://github.com/revyos-ros 存放修改

| https://github.com/revyos-ros/ament_cmake-release.git | zhengjunjie |
| https://github.com/revyos-ros/gazebo_ros_pkgs-release.git | CHEN Xuan |
| https://github.com/revyos-ros/gz_cmake2_vendor-release | zhengjunjie |
| https://github.com/revyos-ros/gz_math6_vendor-release.git | zhengjunjie |
| https://github.com/revyos-ros/libstatistics_collector-release.git | zhengjunjie |
| https://github.com/revyos-ros/mimick_vendor-release.git | zhengjunjie |
| https://github.com/revyos-ros/mrpt2-release.git | zhengjunjie |
| https://github.com/revyos-ros/navigation2-release.git | zhengjunjie |
| https://github.com/revyos-ros/network_interface-release.git | CHEN Xuan |
| https://github.com/revyos-ros/octomap-release.git | zhengjunjie |
| https://github.com/revyos-ros/openni2_camera-release.git | zhengjunjie |
| https://github.com/revyos-ros/osqp_vendor-release.git | zhengjunjie |
| https://github.com/revyos-ros/pal_statistics-release.git | CHEN Xuan |
| https://github.com/revyos-ros/perception_pcl-release.git | CHEN Xuan |
| https://github.com/revyos-ros/qt_gui_core-release.git | CHEN Xuan |
| https://github.com/revyos-ros/rcdiscover-release.git | zhengjunjie |
| https://github.com/revyos-ros/rmw_implementation-release.git | zhengjunjie |
| https://github.com/revyos-ros/ros2_socketcan-release.git | CHEN Xuan |
| https://github.com/revyos-ros/ros2cli-release.git | zhengjunjie |
| https://github.com/revyos-ros/ros_ign-release.git | zhengjunjie |
| https://github.com/revyos-ros/ros_workspace-release.git | zhengjunjie |
| https://github.com/revyos-ros/rosbag2-release.git | zhengjunjie |
| https://github.com/revyos-ros/rqt-release.git | zhengjunjie |
| https://github.com/revyos-ros/rqt_plot-release.git | zhengjunjie |
| https://github.com/revyos-ros/rqt_shell-release.git | zhengjunjie |
| https://github.com/revyos-ros/schunk_svh_library-release.git | CHEN Xuan |
| https://github.com/revyos-ros/tvm_vendor-release.git | CHEN Xuan |
| https://github.com/revyos-ros/webots_ros2-release.git | zhengjunjie |

目标

手里有几个包,想搭建一个 apt 仓库,对应发行版为 Debian unstable riscv64

思路

这里的 server 指的是提供 apt 仓库服务的机器,client 指的是要访问 apt 仓库的机器

  1. (server) 为本机生成 GPG 公钥与私钥
  2. (server) 使用 aptly 添加包,并生成仓库的 snapshot
  3. (server) 使用 nginx 搭建私有的服务器,存放生成的 snapshot
  4. (client) 添加公钥,修改 apt 配置

步骤

1. 生成 gpg-key

生成签名用的 GPG KEY

运行命令: gpg --full-gen-key,按照提示操作。

~/.gnupg/openpgp-revocs.d/目录下生成.rev的 key 文件

生成 ASCII 格式的公钥

gpg --output <key_filename> --armor --export <your_email>

请确保<your_email>与生成私钥时所用的邮箱相同

2. 使用 aptly 添加包,并生成仓库的 snapshot

安装 aptly 后,创建仓库,本步骤执行后将生成~/.aptly目录

1
2
3
# 请拓展尖括号的内容, 例如,我这边拓展后是这样的
# aptly repo create -architectures riscv64 -comment 'private riscv64 repo' -component main -distribution unstable my_repo
aptly repo create -architectures <arch> -comment <your_comment> -component <component> -distribution <distrubution> <repo_name>

向仓库内添加若干包,本步骤执行后将在~/.aptly/pool目录下存放所添加包的拷贝

1
aptly repo add <repo_name> <pkg_path>

创建仓库 snapshot 并发布,此过程会要求使用 GPG 私钥签名,所以会提示输入生成 GPG 私钥时的密码

发布后的仓库在~/.aptly/pool

1
2
aptly snapshot create <snapshot_name> from repo <repo_name>
aptly publish -architectures <arch> snapshot <snapshot_name>

3. 搭建服务器

使用 nginx 搭建文件服务器,使得外部网络可以访问仓库内容

1
2
3
4
5
6
7
8
9
10
sudo apt-get install nginx -y
# 在 root /var/www/html; 一行下面添加下面一行
# autoindex on;
# 注意保留分号
sudo vim /etc/nginx/sites-enabled/default
sudo systemctl restart nginx.service
# 将发布后的 snapshot 仓库移动到服务器目录
sudo cp -r ~/.aptly/public/ /var/www/html/public
# 别忘了用浏览器访问一下
# http://<ip_address>/public

4. 在 client 端访问 apt 仓库

注意,服务器我们已经搭建好了,接下来的操作在客户端上进行

将前文中的公钥添加至本机配置,TODO:这种方法已行将废弃,需要找到更行之有效的方法

1
sudo apt-key add <key_filename>

在 apt 配置中写入如下一行

1
2
# 例如,我这边的是 deb http://10.9.127.200/public/ unstable main
deb <server_path> <distrubution> <component>

已知问题

TODO

  1. 需要对 aptly 进行进一步的解释
  2. 需要对 gpg 签名的原理进行进一步的解释

参考资料

就像是装系统一样,建站的事情来来回回也很多次了,不过只是为了体验建站本身如垦荒一样的过程,以及满足自己对未来的博客文章能够硕果累累的想象。但也同装系统一样,逐渐发觉如果只是这样做,如果只是在重复无法进行积累的事情,那么是无法对自己的过去赋予意义的。活着是为了什么呢?

我暂且把站点的名字取为“你无法体验苹果本身”,只是因为自己对哲学,尤其是对休谟的一些粗浅的兴趣爱好。我们能感受到苹果的颜色、气味、口感,却无法直接感到苹果本身。那么,我们能感受到“我”吗?我们能感受到的,只有自己正在进行的心理活动——快乐,悲伤,愤怒,抑或无聊,我们无法感到一个没有心理活动的自己。思考这种无聊的问题在以前满足了我的虚荣心,到现在……也不能说没有,但我想我应该逐渐发现这件事本身其实已经足够有趣了。所以我把我认为的这里面最有趣的一个问题设为站点的名字。

关于我的 ID ,“ 286 ”已经是很久远的事情了,源自我当时翻得快要背过的小学计算机课本里提到的 80286,我当时认为它很慢(虽然确实很慢),就把这个引申为“小傻瓜”的意思(成年人了要在黑历史前稳住)。“ Sakura ”就已经比较近了,是在大一时读到的《龙族III》里路明非在牛郎店工作时的花名,当时还没有接触日语,错误地把这个读成了带俄语味的/səˈku:la/(并觉得很酷!),直到毕业后有数个朋友跟我说“很难想象一个叫樱花的男人”后,发现这个ID已经到处使用没法再改了才追悔莫及,啊,万恶的历史包袱,万恶的兼容。

基本的介绍就到这里吧,希望这个博客能像自己这几年装系统一样,能活得久一点。