Open34

Linux で Rust の環境構築をするまでに必要なものを 1 つずつインストールしてみる

nukopynukopy

結論

rustup と Rust ツールチェーンと gcclibc6-dev で Hello, world! プログラムの cargo run はできる。

実用的には build-essential を入れた方が良い。

最終的にできた環境は以下:

https://github.com/nukopy/toy-tcpip-rs/tree/main


背景

よく Dockerfile を書いてコンテナを実行するときや、仮想マシン(VM)を作成して設定するとき、build-essential とか cmake とかのビルドに必要なソフトウェアをインストールする設定を書くが、これらが 1 つ 1 つ何のために必要なのかを正確に理解できていない。

なんとなく「コンパイラ、libc、ビルドツール make あたりをインストールする必要があるんだな」くらいの認識はあるが、それぞれ何がインストールされるか、インストールされる 1 つ 1 つのソフトウェアはなぜ必要なのか、まで理解が及んでいないので、一つずつパッケージをインストールしていき「なぜこのソフトウェアをインストールする必要があるのか」を見ていく。

nukopynukopy

目次

メイン

  • VM マシンの起動
  • なにはともあれ rustup をインストールする
  • Rust プロジェクトを作成して Hello, world! する

補足

本題じゃないのでやらない

  • apt-get update
    • apt とは
    • apt-getapt は何が違う?
    • DEBIAN_FRONTEND=noninteractive
nukopynukopy

メモ:同期フォルダの権限エラー

https://docs.getutm.app/guest-support/linux/#fixing-permission-errors

cargo init でディレクトリを作成できない

cd ~/toy-tcpip-rs/workdir
cargo init hello-rust
#     Creating binary (application) package
# error: Failed to create package `hello-rust` at `/home/vagrant/toy-tcpip-rs/workdir/hello-rust`
#
# Caused by:
#   failed to create directory `/home/vagrant/toy-tcpip-rs/workdir/hello-rust`
#
# Caused by:
#   Permission denied (os error 13)
  • Vagrantfile
Vagrant.configure("2") do |config|
  # ...
  # 同期フォルダの設定
  config.vm.synced_folder ".", "/home/vagrant/toy-tcpip-rs",
    # Vagrant UTM プラグインでは default で UTM QEMU VirtFS を同期フォルダの実装として使用している。
    # ref: https://naveenrajm7.github.io/vagrant_utm/features/synced_folders.html
    owner: "vagrant",
    group: "vagrant",
    create: true

  # Provider specific configs
  config.vm.provider "utm" do |u|
    # ...
    # QEMU Directoy Share mode for the VM.
    # Takes none, webDAV or virtFS
    u.directory_share_mode = $vm_directory_share_mode
  end
nukopynukopy
pwd
# /home/vagrant
ls -lah
# --- 出力 ---
total 80K
drwxr-x---  8 vagrant vagrant 4.0K Sep 12 07:40 .
drwxr-xr-x  3 root    root    4.0K Nov 28  2024 ..
-rw-------  1 vagrant vagrant  827 Sep 12 06:51 .bash_history
-rw-r--r--  1 vagrant vagrant  220 Mar 31  2024 .bash_logout
-rw-r--r--  1 vagrant vagrant 3.8K Sep 11 16:51 .bashrc
drwx------  2 vagrant vagrant 4.0K Nov 28  2024 .cache
drwxrwxr-x  3 vagrant vagrant 4.0K Sep 11 16:50 .cargo
-rw-------  1 vagrant vagrant   20 Sep 12 06:56 .lesshst
-rw-r--r--  1 vagrant vagrant  828 Sep 11 16:50 .profile
drwxrwxr-x  6 vagrant vagrant 4.0K Sep 11 16:50 .rustup
drwx------  2 vagrant vagrant 4.0K Sep 11 16:49 .ssh
-rw-r--r--  1 vagrant vagrant    0 Nov 28  2024 .sudo_as_admin_successful
drwxr-xr-x 17 vagrant vagrant  544 Sep 12 06:06 toy-tcpip-rs
-rw-r--r--  1 vagrant vagrant    5 Dec  1  2024 .utm_version
-rw-------  1 vagrant vagrant  26K Nov 29  2024 .viminfo
drwxrwxr-x  2 vagrant vagrant 4.0K Sep 12 07:40 workdir

同期フォルダ ~/toy-tcpip-rs 内の権限を見てみる:

cd toy-tcpip-rs/
pwd
# /home/vagrant/toy-tcpip-rs
ls -lah
# --- 出力 ---
total 32K
drwxr-xr-x 17 vagrant vagrant  544 Sep 12 06:06 .
drwxr-x---  8 vagrant vagrant 4.0K Sep 12 07:46 ..
-rw-r--r--  1     501 dialout  153 Aug  9 07:41 Cargo.lock
-rw-r--r--  1     501 dialout  104 Aug  9 08:28 Cargo.toml
drwxr-xr-x 13     501 dialout  416 Sep 12 06:26 .git
drwxr-xr-x  3     501 dialout   96 Aug  9 07:41 .github
-rw-r--r--  1     501 dialout  173 Sep 11 09:05 .gitignore
-rw-r--r--  1     501 dialout 1.1K Aug  9 07:41 LICENSE
-rw-r--r--  1     501 dialout 2.4K Sep 11 11:24 README.md
-rw-r--r--  1     501 dialout  122 Aug  9 08:24 rust-toolchain.toml
drwxr-xr-x  3     501 dialout   96 Sep 11 11:13 scripts
drwxr-xr-x  3     501 dialout   96 Aug  9 07:41 src
drwxr-xr-x  6     501 dialout  192 Sep 11 06:43 target
drwxr-xr-x  5     501 dialout  160 Sep 11 08:54 .vagrant
-rw-r--r--  1     501 dialout 2.0K Sep 12 07:30 Vagrantfile
drwxr-xr-x  3     501 dialout   96 Sep 11 08:18 .vscode
drwxr-xr-x  3     501 dialout   96 Sep 11 15:09 workdir
nukopynukopy

Vagrant の「同期フォルダ」の仕組み

コピーではなく、マウントを利用。

Vagrant の synced folder(いまの virtFS/9p)では、ホストのフォルダを VM にマウントしているため、ファイルの所有者(UID/GID)もホストの数値のまま見える。そのため macOS の 501:20(= staff)が、そのまま Linux 側では 501:dialoutとして表示される。

※ dialout は gid=20 のエイリアス

Linux は名前ではなく数値 UID/GID で権限判定、つまり「そのディレクトリ、ファイルを読めるか、書き込めるか、実行できるか」が決まるため、vagrant ssh で入ったときのデフォルトのユーザ vagrant (1000:1000, UID=1000, GID=1000) ではマウントされたディレクトリ、ファイルに対しては書き込み、実行権限がない(-rw-r--r-- と表示されているので)。

引用元:ネットワークエンジニアとして | Linux - ファイルのパーミッションと所有者の確認

  • virtFS/9p(UTMの既定):共有マウント。所有者はホストの UID/GID をそのまま提示(または xattr に保持)。chown してもホスト側は変わらないか、拒否される。
  • NFS:共有マウント。やはり UID/GID は数値で突き合せ。map_uid/map_gid を指定すれば合せられる。
  • rsync:ただのコピー。VM 側に新規作成されるので所有者は vagrant になる(ただし基本は片方向、双方向編集には向かない)。同期するには rsync コマンドが必要。
nukopynukopy

Vagrant のファイルの権限を調べる

ホスト側の UID / GID を確認しておく。

  • ユーザ名:nukopy
  • UID:501
  • GID:20 (staff)
whoami
# nukopy

id nukopy
# uid=501(nukopy) gid=20(staff) -> 長いので改行
# groups=20(staff),...

VM に入る

vagrant ssh

以降 VM 内でコマンドを実行:

pwd
# /home/vagrant
# vagrant ssh 直後ではホームディレクトリからセッション開始

Vagrant で作成した VM のデフォルトのユーザ(vagrant upvagrant ssh をしたときに設定されているユーザ)の確認。

whoami
# vagrant
# 現在のユーザは `vagrant` だと分かる

# ユーザ一覧表示
who -aH
# NAME       LINE         TIME             IDLE          PID COMMENT  EXIT
#            system boot  2025-09-12 06:46
#            run-level 5  2025-09-12 06:46
# LOGIN      ttyAMA0      2025-09-12 06:46               813 id=AMA0
# LOGIN      tty1         2025-09-12 06:46               819 id=tty1
# vagrant  + pts/0        2025-09-12 08:20   .          1407 (10.0.2.2)

# UID / GID の確認
id vagrant
# uid=1000(vagrant) gid=1000(vagrant) -> 長いので改行
# groups=1000(vagrant),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),101(lxd)

ユーザ vagrant の UID / GID は 1000 / 1000 ということが分かる。
次にホームディレクトリ配下にマウントしたディレクトリの権限を見ていく。

ホストのディレクトリを VM の /home/vagrant/toy-tcpip-rs にマウントしている。

pwd
/home/vagrant

# --- 出力 ---
total 76
drwxr-x---  7 vagrant vagrant  4096 Sep 12 08:29 .
drwxr-xr-x  3 root    root     4096 Nov 28  2024 ..
-rw-------  1 vagrant vagrant  1601 Sep 12 08:20 .bash_history
-rw-r--r--  1 vagrant vagrant   220 Mar 31  2024 .bash_logout
-rw-r--r--  1 vagrant vagrant  3816 Sep 11 16:51 .bashrc
drwx------  2 vagrant vagrant  4096 Nov 28  2024 .cache
drwxrwxr-x  3 vagrant vagrant  4096 Sep 11 16:50 .cargo
-rw-------  1 vagrant vagrant    20 Sep 12 06:56 .lesshst
-rw-r--r--  1 vagrant vagrant   828 Sep 11 16:50 .profile
drwxrwxr-x  6 vagrant vagrant  4096 Sep 11 16:50 .rustup
drwx------  2 vagrant vagrant  4096 Sep 11 16:49 .ssh
-rw-r--r--  1 vagrant vagrant     0 Nov 28  2024 .sudo_as_admin_successful
# ホームから VM へマウントしたディレクトリ
drwxr-xr-x 16 vagrant vagrant   512 Sep 12 08:18 toy-tcpip-rs
-rw-r--r--  1 vagrant vagrant     5 Dec  1  2024 .utm_version
-rw-------  1 vagrant vagrant 26593 Nov 29  2024 .viminfo

マウントしたディレクトリの情報は以下のようになっている。

drwxr-xr-x 16 vagrant vagrant   512 Sep 12 08:18 toy-tcpip-rs

整理する。

  • ディレクトリ /vagrant/home/toy-tcpip-rs の所有者:vagrant
  • ディレクトリ /vagrant/home/toy-tcpip-rs の所属グループ:vagrant
  • 権限:755
    • 所有者:rwx(4 + 2 + 1 = 7)
    • 所属グループ:r-x(4 + 0 + 1 = 5)
    • その他のユーザ:r-x(4 + 0 + 1 = 5)
nukopynukopy

drwxr-xr-x 16 vagrant vagrant 512 Sep 12 08:18 <dir> の意味

この権限の意味は以下の通り:

  • toy-tcpip-rs というディレクトリの所有者がユーザ vagrant(UID=1000)、所属グループが vagrant(GID=1000)である
  • ユーザ vagrant は、ディレクトリに対して読み・書き・実行(ディレクトリに移動する)権限がある
  • 所属グループ vagrant は、ディレクトリに対して読み・実行(ディレクトリに移動する)権限がある
  • それ以外のユーザは、ディレクトリに対して読み・実行(ディレクトリに移動する)権限がある
ls -la
# ...
# drwxr-xr-x 16 vagrant vagrant 512 Sep 12 08:18 toy-tcpip-rs
# ...
nukopynukopy

ディレクトリ /home/vagrant/toy-tcpip-rs は、VM 作成時に作られ、UID / GID が vagrant / vagrant に設定されている。続いてこのディレクトリ内のディレクトリ、ファイルの権限を見てみる。

cd /home/vagrant/toy-tcpip-rs
ls -la
# --- 出力 ---
total 32
drwxr-xr-x 16 vagrant vagrant  512 Sep 12 08:18 .
drwxr-x---  7 vagrant vagrant 4096 Sep 12 08:29 ..
-rw-r--r--  1     501 dialout  153 Aug  9 07:41 Cargo.lock
-rw-r--r--  1     501 dialout  104 Aug  9 08:28 Cargo.toml
drwxr-xr-x 13     501 dialout  416 Sep 12 06:26 .git
drwxr-xr-x  3     501 dialout   96 Aug  9 07:41 .github
-rw-r--r--  1     501 dialout  173 Sep 11 09:05 .gitignore
-rw-r--r--  1     501 dialout 1077 Aug  9 07:41 LICENSE
-rw-r--r--  1     501 dialout 2373 Sep 11 11:24 README.md
-rw-r--r--  1     501 dialout  122 Aug  9 08:24 rust-toolchain.toml
drwxr-xr-x  3     501 dialout   96 Sep 11 11:13 scripts
drwxr-xr-x  3     501 dialout   96 Aug  9 07:41 src
drwxr-xr-x  6     501 dialout  192 Sep 11 06:43 target
drwxr-xr-x  5     501 dialout  160 Sep 11 08:54 .vagrant
-rw-r--r--  1     501 dialout 1976 Sep 12 07:30 Vagrantfile
drwxr-xr-x  3     501 dialout   96 Sep 11 08:18 .vscode

ざっと見てみると、所有者は 501、所属グループ 20 (= dialout) となっており、ホスト側の UID / GID に一致する。つまり、ホストから VM にディレクトリがマウントされたとき、そのディレクトリ内のディレクトリ、ファイルの権限はホストの権限がそのまま反映されることが分かる。これは mount (virtFS/9p) の方式。

  • ホストのユーザ名:nukopy
  • UID:501
  • GID:20
nukopynukopy

VM の作成

今回は UTM、Vagrant で仮想マシン(VM)を作成する。

UTM は仮想化ソフトウェアと呼ばれるもので、VM を作成したり起動したり管理する役割のソフトウェア。有名なものだと VirtualBox や VMWare がある。Vagrant は仮想化ソフトウェアの違いを吸収してくれる層で、各仮想化ソフトウェアを統一されたインタフェースで扱えるようにしてくれるツールである。正確ではないかもしれないが、ざっくりいうと仮想化ソフトウェアの抽象化レイヤー、ラッパーの立ち位置だと理解すれば OK。

Vagrant とかよくわからんって人は以下などを読むと良いかも。

https://zenn.dev/y_mrok/books/vagrant-no-tsukaikata

ホスト(VM を起動するマシン)

  • OS: macOS 14.7
  • UTM 4.6.5
  • Ruby 3.3.0
  • Vagrant 2.4.9
  • vagrant_utm 0.1.3
  • vagrant-bindfs 1.3.1

https://github.com/gael-ian/vagrant-bindfs?_ebx=1ln5omt4zjz.1756625057.82ord8b

ゲスト(VM)

  • OS: Ubuntu Server 24.04
  • Rust 1.89.0stable (at 2025/06/23)
nukopynukopy

まずは VM の設定を記述する Vagrantfile を作成する。

まだ VM を作っていないので当然だが、以下のコマンドはホストマシン上で実行する。

mkdir workdir
touch Vagrantfile

今回使用する Vagrantfile は以下の通り。

$vm_name = "toy-tcpip"
$vm_cpus = 4
$vm_memory = 4096
$vm_notes = "Vagrant: For toy-tcpip"
$vm_directory_share_mode = "none"
$vm_forwarded_port = 8080
$vm_host = 8080

# ref: https://naveenrajm7.github.io/vagrant_utm/configuration.html
Vagrant.configure("2") do |config|
  # ref: https://portal.cloud.hashicorp.com/vagrant/discover/utm/ubuntu-24.04
  config.vm.box = "utm/ubuntu-24.04"
  config.vm.box_version = "0.0.1"

  # Hostname inside the VM
  config.vm.hostname = $vm_name

  # Ports to forward
  config.vm.network "forwarded_port", guest: $vm_forwarded_port, host: $vm_host

  # Synced folder
  config.vm.synced_folder ".", "/vagrant"

  # Provider specific configs
  config.vm.provider "utm" do |u|
    # Name in UTM UI
    u.name = $vm_name

    # CPU in cores
    u.cpus = $vm_cpus

    # Memory in MB
    u.memory = $vm_memory

    # Notes for UTM VM (Appears in UTM UI)
    u.notes = $vm_notes

    # QEMU Directoy Share mode for the VM.
    # Takes none, webDAV or virtFS
    u.directory_share_mode = $vm_directory_share_mode
  end

  # Provisioner config, supports all built provisioners
  # shell, ansible
  config.vm.provision "shell", inline: <<-SHELL
  # apt-get update
  echo "Hello, world!"
  SHELL
end

config.vm.provision のところに VM の作成(provision)時に実行する処理を書くことができる。インラインでシェルスクリプトを書いたり、初期化用のシェルスクリプトを設定したり、Ansible の設定を置くこともできる。要はサーバの初期セットアップに使われる。

今回の主題はまさにこの VM 作成時に実行される処理に着目している。大体のユースケースでは curl、wget、git などの基本的なツールや、ビルド用のソフトウェアをインストールする処理が記述される。開発用のコンテナを定義する Dockerfile でも似たような処理が散見される。

今回は Rust の Linux 開発環境を作るというのが目的だったので、Rust の環境構築を例として Rust がビルドできるまでに必要なソフトウェアを深堀りしていく。

nukopynukopy

VM の起動

まずは VM を起動する。ここでのコマンドはホストマシン上で実行される。

# ディレクトリ構成の確認
tree workdir
# workdir
# └── Vagrantfile

1 directory, 1 file

# VM を起動
cd workdir
vagrant up

# --- 以下出力 ---
Bringing machine 'default' up with 'utm' provider...
==> default: Checking if box 'utm/ubuntu-24.04' version '0.0.1' is up to date...
==> default: Setting the name of the VM: toy-tcpip
==> default: Clearing any previously set forwarded ports...
==> default: Forwarding ports...
    default: 8080 (guest) => 8080 (host) (adapter 1)
    default: 22 (guest) => 2222 (host) (adapter 1)
==> default: Running 'pre-boot' VM customizations...
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
    default: Guest additions detected
==> default: Setting hostname...
==> default: Mounting shared folders...
    default: /Users/nukopy/Projects/LowLayer/toy-tcpip-rs => /vagrant
==> default: Running provisioner: shell...
    # ここで provision 時のスクリプトが実行される
    default: Running: inline script
    default: Hello, World!

VM が起動できていることを確認する。

vagrant status
# Current machine states:
#
# default                   started (utm)
#
# The VM is started. To stop this VM, you can run `vagrant halt` to
# shut it down forcefully, or you can run `vagrant suspend` to simply
# suspend the virtual machine. In either case, to restart it again,
# simply run `vagrant up`.
nukopynukopy

補足:Vagrant が仮想化ソフトウェアを操作する仕組み

Vagrant は仮想化ソフトウェアの抽象化レイヤである。つまり vagrant xxx というコマンドは操作対象の仮想化ソフトウェア(仮想化の機能を提供する意味で provider と呼ばれる)から提供されている API を使用することで VM の管理をしている。

(TODO: もっと良い図がある。または作成する。)

引用元: Vagrant 公式ドキュメント

仮想マシンを管理するための主要な機能として仮想マシンの起動、停止、削除があるが、 UTM の場合、Vagrant は UTM が提供する utmctl という CLI を介してこれを実行する。Vagrant のコマンドと utmctl の対応の対応は以下の表の通り。これは Vagrant の UTM 用のプラグイン vagrant-utm のドキュメントより引用。

https://naveenrajm7.github.io/vagrant_utm/

引用元:Vagrant UTM

vagrant-utm のコマンド呼び出し部分を見てみる。

  • vagrant start -> utmctl start

https://github.com/naveenrajm7/vagrant_utm/blob/fafcab356266a8b6bcb06ffdb5c1400441d81a9b/lib/vagrant_utm/driver/version_4_5.rb#L261-L263

  • execute 関数

https://github.com/naveenrajm7/vagrant_utm/blob/fafcab356266a8b6bcb06ffdb5c1400441d81a9b/lib/vagrant_utm/driver/base.rb#L248-L309

UTM に限らず、各 provider が VM を管理する API を提供し、provider ごとのプラグインがそれを実行することで Vagrant が成り立っている(はず)。

https://github.com/hashicorp/vagrant/wiki/available-vagrant-plugins#providers

nukopynukopy

ちなみに UTM では以下 3 種類の API を提供している。

The plugin invokes UTM API inorder to implement Vagrant required actions. As there are several ways to control UTM, we use the following order

  1. UTM Command Line (utmctl)
  2. Apple Scripting Bridge (osascript)
  • 2.a Applescript
  • 2.b JavaScript
  1. Shell command

なぜ 3 種類あるかというと、UTM では CLI である utmctl でカバーできない機能があるためである。vagrant-utm の作者は、

the goal of this plugin is to use UTM command line tool utmctl as single point of control.

のように「CLI ツールである utmctl を唯一の制御点とすることがゴールである」と書いたうえで、現状 CLI ツールでカバーできていない領域を osascript やシェルコマンドに頼らざるを得ないことになっている。こればっかり公式頼みになってしまうので、迂回策を取らざるを得ない。

https://naveenrajm7.github.io/vagrant_utm/internals/utm_api.html#:~:text=the goal of this plugin is to use UTM command line tool utmctl as single point of control.)

nukopynukopy

なにはともあれ rustup をインストールする: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Vagrant で起動した VM の中に入る

VM が起動したので、早速 VM の中に入って Rust をインストールしてみる。

Vagrant では、VM に SSH で接続するための便利なコマンド vagrant ssh が提供されている。

# VM 起動
cd workdir
vagrant up

# VM へ SSH へ接続
vagrant ssh

# --- 以下 VM 環境内 ---
lsb_release -a
# No LSB modules are available.
# Distributor ID: Ubuntu
# Description:    Ubuntu 24.04.1 LTS
# Release:        24.04
# Codename:       noble

uname -srvmpio
# Linux 6.8.0-49-generic #49-Ubuntu SMP PREEMPT_DYNAMIC Sun Nov  3 21:21:58 UTC 2024 aarch64 aarch64 aarch64 GNU/Linux

起動したときの様子。Ubuntu 24.04 の VM に接続でき、プロンプトが表示されている。接続直後のカレントディレクトリはホームディレクトリ /home/vagrant となっている。

補足:VSCode の Remote SSH 拡張を利用する

ちなみに、VSCode の Remote SSH のような機能で接続したい場合、vagrant ssh-config というコマンドで ssh config を取得できる。vagrant ssh はこの情報を使っている。

vagrant ssh-config
# Host default
#   HostName 127.0.0.1
#   User vagrant
#   Port 2222
#   UserKnownHostsFile /dev/null
#   StrictHostKeyChecking no
#   PasswordAuthentication no
#   IdentityFile /Users/nukopy/Projects/LowLayer/toy-tcpip-rs/.vagrant/machines/default/utm/private_key
#   IdentitiesOnly yes
#   LogLevel FATAL
#   PubkeyAcceptedKeyTypes +ssh-rsa
#   HostKeyAlgorithms +ssh-rsa

Remote SSH の設定に ssh config を追加した様子は以下の通り(図中の toy-tcpip という名前の接続情報が SSH TARGETS に表示されている)。これで VM に拡張を使って接続できるようになる。

nukopynukopy

rustup のインストール

rustup をインストールする。

rustup は Rust コンパイラ、リンター、フォーマッターを含めた Rust でソフトウェア開発を行うために必要なツールチェーン(Rust の開発に必要なツール一式、Rust toolchains)を管理するための CLI ツール。

https://www.rust-lang.org/ja/tools/install

以下のコマンドを実行する:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

出力

info: downloading installer

Welcome to Rust!

This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.

Rustup metadata and toolchains will be installed into the Rustup
home directory, located at:

  /home/vagrant/.rustup

This can be modified with the RUSTUP_HOME environment variable.

The Cargo home directory is located at:

  /home/vagrant/.cargo

This can be modified with the CARGO_HOME environment variable.

The cargo, rustc, rustup and other commands will be added to
Cargo's bin directory, located at:

  /home/vagrant/.cargo/bin

This path will then be added to your PATH environment variable by
modifying the profile files located at:

  /home/vagrant/.profile
  /home/vagrant/.bashrc

You can uninstall at any time with rustup self uninstall and
these changes will be reverted.

Current installation options:


   default host triple: aarch64-unknown-linux-gnu
     default toolchain: stable (default)
               profile: default
  modify PATH variable: yes

1) Proceed with standard installation (default - just press enter)
2) Customize installation
3) Cancel installation
>1

info: profile set to 'default'
info: default host triple is aarch64-unknown-linux-gnu
info: syncing channel updates for 'stable-aarch64-unknown-linux-gnu'
info: latest update on 2025-08-07, rust version 1.89.0 (29483883e 2025-08-04)
info: downloading component 'cargo'
  9.2 MiB /   9.2 MiB (100 %)   4.5 MiB/s in  2s         
info: downloading component 'clippy'
info: downloading component 'rust-docs'
 20.2 MiB /  20.2 MiB (100 %)   3.4 MiB/s in  6s         
info: downloading component 'rust-std'
 26.6 MiB /  26.6 MiB (100 %) 539.4 KiB/s in 27s         
info: downloading component 'rustc'
 58.2 MiB /  58.2 MiB (100 %) 494.0 KiB/s in  1m 15s         
info: downloading component 'rustfmt'
info: installing component 'cargo'
  9.2 MiB /   9.2 MiB (100 %)   6.9 MiB/s in  1s         
info: installing component 'clippy'
info: installing component 'rust-docs'
 20.2 MiB /  20.2 MiB (100 %)   3.1 MiB/s in  5s         
info: installing component 'rust-std'
 26.6 MiB /  26.6 MiB (100 %)   5.9 MiB/s in  4s         
info: installing component 'rustc'
 58.2 MiB /  58.2 MiB (100 %)   6.0 MiB/s in  9s         
info: installing component 'rustfmt'
info: default toolchain set to 'stable-aarch64-unknown-linux-gnu'

  stable-aarch64-unknown-linux-gnu installed - rustc 1.89.0 (29483883e 2025-08-04)


Rust is installed now. Great!

To get started you may need to restart your current shell.
This would reload your PATH environment variable to include
Cargo's bin directory ($HOME/.cargo/bin).

To configure your current shell, you need to source
the corresponding env file under $HOME/.cargo.

This is usually done by running one of the following (note the leading DOT):
. "$HOME/.cargo/env"            # For sh/bash/zsh/ash/dash/pdksh
source "$HOME/.cargo/env.fish"  # For fish
source $"($nu.home-path)/.cargo/env.nu"  # For nushell

続いて、cargo の bin ディレクトリに PATH を通す。

# set cargo path
source $HOME/.cargo/env

# add to .bashrc
echo 'source $HOME/.cargo/env' >> $HOME/.bashrc

ちなみに $HOME/.cargo/env の中身はシェルスクリプトになっている。

  • /home/vagrant/.cargo/env
#!/bin/sh
# rustup shell setup
# affix colons on either side of $PATH to simplify matching
case ":${PATH}:" in
    *:"$HOME/.cargo/bin":*)
        ;;
    *)
        # Prepending path in case a system-installed rustc needs to be overridden
        export PATH="$HOME/.cargo/bin:$PATH"
        ;;
esac

$HOME/.cargo/bin には rustup をはじめ、Rust のツールチェーンが置かれている。ここにパスを通すことで、cargo コマンドなど Rust の開発でよく使うコマンドが実行できるようになる。

ls ~/.cargo/bin
# cargo         cargo-fmt   clippy-driver  rust-analyzer  rustdoc  rust-gdb     rust-lldb
# cargo-clippy  cargo-miri  rls            rustc          rustfmt  rust-gdbgui  rustup

cargorustc のバージョンが取得できれば PATH が通っていることが確認できる。
これで Rust のプロジェクトを始める準備ができた。

rustup -V
# rustup 1.28.2 (e4f3ad6f8 2025-04-28)
# info: This is the version for the rustup toolchain manager, not the rustc compiler.
# info: The currently active `rustc` version is `rustc 1.89.0 (29483883e 2025-08-04)`

cargo -V
# cargo 1.89.0 (c24e10642 2025-06-23)

rustc -V
# rustc 1.89.0 (29483883e 2025-08-04)
nukopynukopy

補足:uname コマンド

uname コマンドは現在カーネルのバージョンなど、システムの情報を表示するためのコマンド。

以下に man uname の出力を示す:

先に実行した uname -srvmpio の意味は以下の通り。-a オプションを使用すると全ての情報を出力する。

UNAME(1)                                      User Commands                                     UNAME(1)

NAME
       uname - print system information

SYNOPSIS
       uname [OPTION]...

DESCRIPTION
       Print certain system information.  With no OPTION, same as -s.

       -a, --all
              print all information, in the following order, except omit -p and -i if unknown:

       -s, --kernel-name
              print the kernel name

       -n, --nodename
              print the network node hostname

       -r, --kernel-release
              print the kernel release

       -v, --kernel-version
              print the kernel version

       -m, --machine
              print the machine hardware name

       -p, --processor
              print the processor type (non-portable)

       -i, --hardware-platform
              print the hardware platform (non-portable)

       -o, --operating-system
              print the operating system

       --help display this help and exit

       --version
              output version information and exit

AUTHOR
       Written by David MacKenzie.

REPORTING BUGS
       GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
       Report any translation bugs to <https://translationproject.org/team/>

COPYRIGHT
       Copyright  ©  2023  Free  Software  Foundation,  Inc.  License GPLv3+: GNU GPL version 3 or later
       <https://gnu.org/licenses/gpl.html>.
       This is free software: you are free to change and redistribute it.  There is NO WARRANTY, to  the
       extent permitted by law.

SEE ALSO
       arch(1), uname(2)

       Full documentation <https://www.gnu.org/software/coreutils/uname>
       or available locally via: info '(coreutils) uname invocation'

GNU coreutils 9.4                              April 2024                                       UNAME(1)
nukopynukopy

Rust プロジェクトを作成して Hello, world! する

VM の作成

ごちゃごちゃしたので改めて VM の構成ファイル Vagrantfile は以下の通り:

  • Vagrantfile
$vm_name = "toy-tcpip"
$vm_cpus = 4
$vm_memory = 4096
$vm_notes = "VM for toy-tcpip"
$vm_directory_share_mode = "virtFS"
$vm_forwarded_port = 80
$vm_host_port = 8080

# for synced folder
$base_dir = "/home/vagrant"
$sync_dir = "#{$base_dir}/_mnt_toy-tcpip-rs"
$work_dir = "#{$base_dir}/toy-tcpip-rs"

# ref: https://naveenrajm7.github.io/vagrant_utm/configuration.html
Vagrant.configure("2") do |config|
  # ref: https://portal.cloud.hashicorp.com/vagrant/discover/utm/ubuntu-24.04
  config.vm.box = "utm/ubuntu-24.04"
  config.vm.box_version = "0.0.1"

  # Hostname inside the VM
  config.vm.hostname = $vm_name

  # Ports to forward
  # localhost:80 access is forwarded to <vm>:8080
  config.vm.network "forwarded_port", guest: $vm_forwarded_port, host: $vm_host_port

  # Synced folder
  config.vm.synced_folder ".", $sync_dir, create: true

  # 501:20 → 1000:1000 に見せる
  config.bindfs.bind_folder $sync_dir, $work_dir,
    map: "501/1000:@20/@1000", perms: "u=rwX:g=rwX:o=rX", create_as_user: true

  # Provider specific configs
  config.vm.provider "utm" do |u|
    # Name in UTM UI
    u.name = $vm_name

    # Machine type
    # not supported on vagrant_utm plugin
    # u.machine = "vf"

    # CPU in cores
    u.cpus = $vm_cpus

    # Memory in MB
    u.memory = $vm_memory

    # Notes for UTM VM (Appears in UTM UI)
    u.notes = $vm_notes

    # QEMU Directoy Share mode for the VM.
    # Takes none, webDAV or virtFS
    u.directory_share_mode = $vm_directory_share_mode
  end

  # Provisioner config, supports all built provisioners
  # ref: https://developer.hashicorp.com/vagrant/docs/provisioning/shell
  config.vm.provision "shell", privileged: false, path: "scripts/bootstrap.sh"
end

VM 作成時に実行するスクリプトは以下の通り:

  • scripts/bootstrap.sh
    • Rust のインストールまでを書いてある
#!/bin/bash

set -exu pipefail

export DEBIAN_FRONTEND=noninteractive

echo "Bootstrapping VM for toy-tcpip-rs..."
echo "Run script as non-root user: whoami=$(whoami)"

# install packages
sudo apt-get update -y

# install rust non interactive
# ref: https://github.com/rust-lang-deprecated/rustup.sh/issues/83
curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y

# set cargo path
source $HOME/.cargo/env
echo "cargo version: $(cargo --version)"

# add to bashrc to add Rust toolchains to PATH
echo "source $HOME/.cargo/env" >> $HOME/.bashrc

set +exuo pipefail

echo "Bootstrap script completed successfully!"
nukopynukopy

Rust プロジェクトを作成して Hello, world! する

VM に接続し、Rust プロジェクトを作成し、Hello, world! と出力されるプログラムを実行する。

まずは VM へ接続:

vagrant up
vagrant ssh

以下 VM 内でコマンドを実行する。
作業ディレクトリ ~/toy-tcpip-rsVagrantfile に設定してあるとおり。これはホームディレクトリ配下であればどこでも良い。

# --- 以下 VM 内で実行 ---
# 作業ディレクトリへ移動
cd ~/toy-tcpip-rs

# rust-toochain.toml の確認(事前に用意したもの)
cat rust-toolchain.toml
# [toolchain]
# # Rust stable version is 1.89.0 at 2025.09.13 (ref: https://releases.rs/)
# channel = "1.89.0"
# components = ["rustfmt", "clippy", "rust-analyzer"]
# targets = ["aarch64-unknown-linux-gnu"]

# Rust プロジェクトの作成
cargo init .
# --- ログ ---
# 初回 cargo init 時は各ツールチェーンのインストールが走る
info: syncing channel updates for '1.89.0-aarch64-unknown-linux-gnu'
info: latest update on 2025-08-07, rust version 1.89.0 (29483883e 2025-08-04)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-analyzer'
info: downloading component 'rust-docs'
info: downloading component 'rust-std'
info: downloading component 'rustc'
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-analyzer'
info: installing component 'rust-docs'
info: installing component 'rust-std'
info: installing component 'rustc'
info: installing component 'rustfmt'
    Creating binary (application) package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

# cargo init で作成されたファイルの確認
ls
# scripts と Vagrantfile はホストからマウントされたもの
# Cargo.lock  LICENSE    rust-toolchain.toml  src     Vagrantfile
# Cargo.toml  README.md  scripts              target

Rust プロジェクトの初期化までできた。初期化時には Hello, world! を出力するプログラムが作成されているので、早速これを実行してみる。

# src/main.rs の中身の確認
cat src/main.rs 
# fn main() {
#     println!("Hello, world!");
# }

# 実行 (build & execute)
cargo run
# error: linker `cc` not found
#   |
#   = note: No such file or directory (os error 2)
# 
# error: could not compile `toy-tcpip-rs` (bin "toy-tcpip-rs") due to 1 previous error

error: linker cc not found というエラーメッセージとともにプログラムの実行が失敗する。
このエラーで検索すると大体 GCC (GNU C Compiler) を入れろとか、build-essential を入れろとか書いてある。

https://stackoverflow.com/questions/52445961/how-do-i-fix-the-rust-error-linker-cc-not-found-for-debian-on-windows-10

nukopynukopy

builld-essential を入れるとコンパイルできるのは分かっているが、これらを単体でインストールしていくとどうなるのか試してみたい。

build-essential の構成要素は以下の通り。
まずは cc がないと言われたので、build-essential の中の gcc をインストールしてみる。

  • dpkg-dev (>= 1.17.11)
    • Debian package development tools
  • g++ (>= 4:10.2)
    • GNU C++ compiler
  • gcc (>= 4:10.2)
    • GNU C compiler
  • libc
    • libc6-dev
      • GNU C Library: Development Libraries and Header Files
    • or libc-dev
      • virtual package provided by libc6-dev
  • make
    • utility for directing compilation

https://packages.ubuntu.com/jammy/build-essential

nukopynukopy

sudo apt install gcc

あえて build-essential ではなく、gcc のみをインストールしてみる。

sudo apt update -y && sudo apt install -y gcc

# --- ログ ---
# apt update のログは省略
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  binutils binutils-aarch64-linux-gnu binutils-common cpp cpp-13 cpp-13-aarch64-linux-gnu
  cpp-aarch64-linux-gnu fontconfig-config fonts-dejavu-core fonts-dejavu-mono gcc-13
  gcc-13-aarch64-linux-gnu gcc-13-base gcc-aarch64-linux-gnu libaom3 libasan8 libatomic1
  libbinutils libc-bin libc-dev-bin libc-devtools libc6 libc6-dev libcc1-0 libcrypt-dev
  libctf-nobfd0 libctf0 libde265-0 libdeflate0 libfontconfig1 libgcc-13-dev libgd3 libgomp1
  libgprofng0 libheif-plugin-aomdec libheif-plugin-aomenc libheif-plugin-libde265 libheif1
  libhwasan0 libisl23 libitm1 libjbig0 libjpeg-turbo8 libjpeg8 liblerc4 liblsan0 libmpc3
  libsframe1 libsharpyuv0 libtiff6 libtsan2 libubsan1 libwebp7 libx11-6 libx11-data libxau6
  libxcb1 libxdmcp6 libxpm4 linux-libc-dev linux-tools-common locales manpages-dev
  rpcsvc-proto
Suggested packages:
  binutils-doc gprofng-gui cpp-doc gcc-13-locales cpp-13-doc gcc-multilib make autoconf
  automake libtool flex bison gdb gcc-doc gcc-13-doc gdb-aarch64-linux-gnu glibc-doc
  libnss-nis libnss-nisplus libgd-tools libheif-plugin-x265 libheif-plugin-ffmpegdec
  libheif-plugin-jpegdec libheif-plugin-jpegenc libheif-plugin-j2kdec libheif-plugin-j2kenc
  libheif-plugin-rav1e libheif-plugin-svtenc
The following NEW packages will be installed:
  binutils binutils-aarch64-linux-gnu binutils-common cpp cpp-13 cpp-13-aarch64-linux-gnu
  cpp-aarch64-linux-gnu fontconfig-config fonts-dejavu-core fonts-dejavu-mono gcc gcc-13
  gcc-13-aarch64-linux-gnu gcc-13-base gcc-aarch64-linux-gnu libaom3 libasan8 libatomic1
  libbinutils libc-dev-bin libc-devtools libc6-dev libcc1-0 libcrypt-dev libctf-nobfd0
  libctf0 libde265-0 libdeflate0 libfontconfig1 libgcc-13-dev libgd3 libgomp1 libgprofng0
  libheif-plugin-aomdec libheif-plugin-aomenc libheif-plugin-libde265 libheif1 libhwasan0
  libisl23 libitm1 libjbig0 libjpeg-turbo8 libjpeg8 liblerc4 liblsan0 libmpc3 libsframe1
  libsharpyuv0 libtiff6 libtsan2 libubsan1 libwebp7 libx11-6 libx11-data libxau6 libxcb1
  libxdmcp6 libxpm4 linux-libc-dev manpages-dev rpcsvc-proto
The following packages will be upgraded:
  libc-bin libc6 linux-tools-common locales
4 upgraded, 61 newly installed, 0 to remove and 217 not upgraded.
Need to get 66.7 MB of archives.

再度 cargo run を実行。Hello, world! が出力された。

Hello, world! を出力するための println マクロは標準出力なので libc 必要ではと思ったけど、当然 gcc の依存関係に含まれている。上記のログの libc6-dev というのがこれ。

cargo run
#    Compiling toy-tcpip-rs v0.1.0 (/home/vagrant/toy-tcpip-rs)
#     Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.45s
#      Running `target/debug/toy-tcpip-rs`
# Hello, world!
nukopynukopy

gcc の依存関係を見る

apt でインストールした gcc の依存関係を見ていく。

まずはパッケージのメタデータ。Recommends というところに libc6-dev と書かれている。

dpkg -s gcc

# --- output ---
Package: gcc
Status: install ok installed
Priority: optional
Section: devel
Installed-Size: 37
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Architecture: arm64
Source: gcc-defaults (1.214ubuntu1)
Version: 4:13.2.0-7ubuntu1
Provides: c-compiler
Depends: cpp (= 4:13.2.0-7ubuntu1), cpp-aarch64-linux-gnu (= 4:13.2.0-7ubuntu1), gcc-13 (>= 13.2.0-11~), gcc-aarch64-linux-gnu (= 4:13.2.0-7ubuntu1)
Recommends: libc6-dev | libc-dev
Suggests: gcc-multilib, make, manpages-dev, autoconf, automake, libtool, flex, bison, gdb, gcc-doc
Conflicts: gcc-doc (<< 1:2.95.3)
Description: GNU C compiler
 This is the GNU C compiler, a fairly portable optimizing compiler for C.
 .
 This is a dependency package providing the default GNU C compiler.
Original-Maintainer: Debian GCC Maintainers <debian-gcc@lists.debian.org>

もう少し分かりやすく出力したものがこれ:

apt-cache depends gcc

# --- output ---
gcc
  Depends: cpp
  Depends: cpp-aarch64-linux-gnu
  Depends: gcc-13
  Depends: gcc-aarch64-linux-gnu
  Conflicts: gcc-doc
 |Recommends: libc6-dev
  Recommends: <libc-dev>
    libc6-dev
  Suggests: <gcc-multilib>
  Suggests: make
    make-guile
  Suggests: manpages-dev
  Suggests: autoconf
  Suggests: automake
  Suggests: libtool
  Suggests: flex
  Suggests: bison
  Suggests: gdb
  Suggests: gcc-doc

libc6-dev の依存関係を見てみる:

dpkg -s libc6-dev

# --- output ---
Package: libc6-dev
Status: install ok installed
Priority: optional
Section: libdevel
Installed-Size: 9456
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Architecture: arm64
Multi-Arch: same
Source: glibc
Version: 2.39-0ubuntu8.5
Replaces: libc6 (<< 2.38-1ubuntu1)
Provides: libc-dev (= 2.39-0ubuntu8.5)
Depends: libc6 (= 2.39-0ubuntu8.5), libc-dev-bin (= 2.39-0ubuntu8.5), linux-libc-dev, libcrypt-dev, rpcsvc-proto
Suggests: glibc-doc, manpages-dev
Breaks: binutils (<< 2.38), catch (<< 1.12.2-0.1), heimdal-multidev (<= 7.7.0+dfsg-4), igblast (<= 1.19.0-1), libassimp-dev (<= 5.2.4~ds0-1), libasyncns-dev (<= 0.8-6build2), libatm1-dev (<= 1:2.5.1-4), libaws20-dev (<= 20.2-2+b1), libboinc-app-dev (<= 7.20.2+dfsg-1), libbson-dev (<= 1.22.0-1), libc6-dev-arm64-cross (<< 2.39~), libcups2-dev (<= 2.4.2-1), libdeal.ii-dev (<= 9.4.0-1), libdkim-dev (<= 1:1.0.21-4build3), libdolfin-dev-common (<= 2019.2.0~git20220407.d29e24d-5), libeckit-dev (<= 1.20.0-1), libfclib-dev (<= 3.1.0+dfsg-2), libfltk1.3-dev (<= 1.3.8-4+b1), libghc-resolv-dev (<= 0.1.2.0-3), libghc-resolv-prof (<= 0.1.2.0-3), libglib2.0-dev (<= 2.72.3-1), libgloox-dev (<= 1.0.24-2+b1), libhesiod-dev (<= 3.2.1-3.1+b1), libinfinity-0.7-dev (<= 0.7.2-1build1), libinsighttoolkit4-dev (<= 4.13.3withdata-dfsg2-3+b1), libinsighttoolkit5-dev (<= 5.2.1-5+b1), libismrmrd-dev (<= 1.8.0-2), libldap-dev (<= 2.5.12+dfsg-2), liblog4cplus-dev (<= 2.0.7-1), libloudmouth1-dev (<= 1.5.4-1), libmgl-dev (<= 8.0.1-2), libmimalloc2.0 (<= 2.0.6+ds-1), libminc-dev (<= 2.4.03-5), libmongoc-dev (<= 1.22.1-1), libmrpt-ros1bridge-dev (<= 1:2.4.9+ds-4+b2), libmysqlclient-dev (<= 8.0.29-1), libnetcdf-dev (<= 1:4.9.0-3), libnetcdf-mpi-dev (<= 1:4.9.0-1), libnetcdf-pnetcdf-dev (<= 1:4.9.0-1), libnfsidmap-dev (<= 1:2.6.1-2), libns3-dev (<= 3.36.1+dfsg-4), libola-dev (<= 0.10.8.nojsmin-2), libopenafs-dev (<= 1.8.8.1-3), libopendkim-dev (<= 2.11.0~beta2-7), libopendmarc-dev (<= 1.4.2-1), libopenms-dev (<= 2.6.0+cleaned1-3build4), libopenzwave1.6-dev (<= 1.6.1914+ds-1), libpg-query-dev (<= 13-2.1.2-2), librbl-dev (<= 2.11.0~beta2-7), libre-dev (<= 1.1.0-1build1), libshishi-dev (<= 1.0.2-11), libslurm-dev (<= 21.08.8.2-1), libsocksd0-dev (<= 1.4.2+dfsg-7build7), libspf2-dev (<= 1.2.10-7.1+b1), libstrophe-dev (<= 0.12.1-2), libtaningia-dev (<= 0.2.2-2), libtrilinos-amesos-dev (<= 13.2.0-3), libtrilinos-amesos2-dev (<= 13.2.0-3), libtrilinos-anasazi-dev (<= 13.2.0-3), libtrilinos-aztecoo-dev (<= 13.2.0-3), libtrilinos-belos-dev (<= 13.2.0-3), libtrilinos-epetra-dev (<= 13.2.0-3), libtrilinos-epetraext-dev (<= 13.2.0-3), libtrilinos-galeri-dev (<= 13.2.0-3), libtrilinos-ifpack-dev (<= 13.2.0-3), libtrilinos-ifpack2-dev (<= 13.2.0-3), libtrilinos-intrepid-dev (<= 13.2.0-3), libtrilinos-intrepid2-dev (<= 13.2.0-3), libtrilinos-isorropia-dev (<= 13.2.0-3), libtrilinos-kokkos-dev (<= 13.2.0-3), libtrilinos-kokkos-kernels-dev (<= 13.2.0-3), libtrilinos-komplex-dev (<= 13.2.0-3), libtrilinos-ml-dev (<= 13.2.0-3), libtrilinos-moertel-dev (<= 13.2.0-3), libtrilinos-muelu-dev (<= 13.2.0-3), libtrilinos-nox-dev (<= 13.2.0-3), libtrilinos-phalanx-dev (<= 13.2.0-3), libtrilinos-pike-dev (<= 13.2.0-3), libtrilinos-piro-dev (<= 13.2.0-3), libtrilinos-pliris-dev (<= 13.2.0-3), libtrilinos-rol-dev (<= 13.2.0-3), libtrilinos-rtop-dev (<= 13.2.0-3), libtrilinos-rythmos-dev (<= 13.2.0-3), libtrilinos-sacado-dev (<= 13.2.0-3), libtrilinos-shylu-dev (<= 13.2.0-3), libtrilinos-stokhos-dev (<= 13.2.0-3), libtrilinos-stratimikos-dev (<= 13.2.0-3), libtrilinos-teko-dev (<= 13.2.0-3), libtrilinos-teuchos-dev (<= 13.2.0-3), libtrilinos-thyra-dev (<= 13.2.0-3), libtrilinos-tpetra-dev (<= 13.2.0-3), libtrilinos-trilinoscouplings-dev (<= 13.2.0-3), libtrilinos-triutils-dev (<= 13.2.0-3), libtrilinos-xpetra-dev (<= 13.2.0-3), libtrilinos-zoltan2-dev (<= 13.2.0-3), libvbr-dev (<= 2.11.0~beta2-7), libvisp-dev (<= 3.5.0-2+b1), libvotca-dev (<= 2022-3), libvtk6-dev (<= 6.3.0+dfsg2-8.1+b1), libvtk7-dev (<= 7.1.1+dfsg2-10.2), open-vm-tools-dev (<= 2:12.0.5-2), pidgin-librvp (<= 0.9.7cvs-3), proftpd-dev (<= 1.3.7d+dfsg-2), slurm-wlm-basic-plugins-dev (<= 21.08.8.2-1)
Conflicts: libc0.3-dev, libc6.1-dev
Description: GNU C Library: Development Libraries and Header Files
 Contains the symlinks, headers, and object files needed to compile
 and link programs which use the standard C library.
Homepage: https://www.gnu.org/software/libc/libc.html
nukopynukopy

cargo run で生成されたバイナリを調べる

続いて、cargo run で生成されたバイナリを調べる。cargo run を実行すると、カレントディレクトリの target/debug/<package name> に実行バイナリが生成される。

ls -la target/debug

# --- output
total 3792
drwxrwxr-x 10 vagrant vagrant     320 Sep 13 13:43 .
drwxrwxr-x  5 vagrant vagrant     160 Sep 13 13:43 ..
drwxrwxr-x  2 vagrant vagrant      64 Sep 13 13:43 build
-rw-rw-r--  1 vagrant vagrant       0 Sep 13 13:43 .cargo-lock
drwxrwxr-x  4 vagrant vagrant     128 Sep 13 13:43 deps
drwxrwxr-x  2 vagrant vagrant      64 Sep 13 13:43 examples
drwxrwxr-x  3 vagrant vagrant      96 Sep 13 13:43 .fingerprint
drwxrwxr-x  3 vagrant vagrant      96 Sep 13 13:43 incremental
# ↓ これ ↓
-rwxrwxr-x  2 vagrant vagrant 3877088 Sep 13 13:43 toy-tcpip-rs
-rw-rw-r--  1 vagrant vagrant      93 Sep 13 13:43 toy-tcpip-rs.d

動的リンクを調べる

Hello, world! の出力には write システムコールが必要なので、libc が動的リンクされているはず。実行ファイルが動的リンクするライブラリを ldd コマンドで一覧してみる。

https://ja.manpages.org/ldd

https://man7.org/linux/man-pages/man1/ldd.1.html

ldd target/debug/toy-tcpip-rs

# --- output ---
        linux-vdso.so.1 (0x0000ebc402c5e000)
        libgcc_s.so.1 => /lib/aarch64-linux-gnu/libgcc_s.so.1 (0x0000ebc402b70000)
        libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000ebc4029b0000)
        /lib/ld-linux-aarch64.so.1 (0x0000ebc402c21000)

libc.so.6 というのがまさに libc がリンクされているところ。

nukopynukopy

解釈が分からないので ChatGPT 5 を頼る。


行ごとの意味

ldd target/debug/toy-tcpip-rs

# --- output ---
        linux-vdso.so.1 (0x0000ebc402c5e000)
        libgcc_s.so.1 => /lib/aarch64-linux-gnu/libgcc_s.so.1 (0x0000ebc402b70000)
        libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000ebc4029b0000)
        /lib/ld-linux-aarch64.so.1 (0x0000ebc402c21000)

linux-vdso.so.1 (0x0000ebc402c5e000)

  • vDSO(virtual DSO)。ファイルはディスクに存在しない、カーネルが直接プロセスに挿す共有オブジェクト
  • gettimeofday など一部の syscalls をユーザー空間で高速化するための入り口。
  • 右側のアドレスはマッピング先(毎回変わる=ASLR)。パスが無いのは“仮想”だから。

libgcc_s.so.1 => /lib/aarch64-linux-gnu/libgcc_s.so.1

  • GCC の低レベルランタイム。代表例は
    • 例外/スタックアンワインドのルーチン(_Unwind_*
    • いくつかの算術ビルトイン(アーキ依存)
  • Rust バイナリでも panic=unwind のときや、一部ビルトインの都合で NEEDED に乗ることがある
    panic=abort にすると外れることも多いが、ケース次第)

libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6

  • glibc 本体(C ランタイム/標準ライブラリ)write(2) など OS への呼び出しの窓口
  • println! は最終的に 標準出力へ書く → libc 経由でカーネルへ、なのでここに依存。

/lib/ld-linux-aarch64.so.1

  • 動的リンカ/ローダ(ELF インタプリタ)。プロセス起動時に読み込まれ、

    • 依存 .so を探す
    • シンボル解決・再配置を行う
    • その後に _start へ制御を渡す
  • バイナリの PT_INTERP フィールドでこのパスが指定されている。
    ldd では “解決先”ではなくコイツ自身が行として出る)

補足:/lib/aarch64-linux-gnu/Debian/Ubuntu の Multi-Arch ディレクトリ。アーキ別の標準ライブラリ置き場。


これらが示す“起動の流れ”(ざっくり)

  1. execve で起動
  2. カーネルが PT_INTERP を読んで 動的リンカ /lib/ld-linux-aarch64.so.1 をロード
  3. 動的リンカが libgcc_s.so.1libc.so.6 を解決・マップ
  4. カーネルが vDSO をマップ
  5. すべて解決後、_start → Rust ランタイム → main 実行

この4行、要するに「カーネルの高速化おまけ(vDSO)+ C ランタイム(glibc)+ GCC ランタイム(場合により)+ 動的ローダ」ってこと。
ここまで見えたら、もう依存関係の“骨格”は掴めてる。次は readelf -d の生ログを並べて、「NEEDED と ldd の対応表」を作ってもいいよ。

nukopynukopy

続いて ELF(Linux の実行ファイル形式)の「動的セクション(Dynamic Section)」を生で読んでみる。

以下のコマンドにより、実行ファイルの中には「依存ライブラリの名前」が書かれていることが分かる。ただし、依存ライブラリのパスまでは分からない。名前解決は実行時リンカ (ld-linux) がやる。

readelf -d target/debug/toy-tcpip-rs | grep NEEDED

# --- output ---
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]

出力結果を見ると、target/debug/toy-tcpip-rs が共有ライブラリ libc.so.6 に依存していることが分かる。

nukopynukopy

続いて write システムコールの存在をバイナリの中で確認したい。

Rust バイナリには write システムコールの実装はなく、動的ライブラリである libc がこの実装を提供してくれる。ここでは、動的ライブラリ(共有ライブラリ)libc.so.6 内の write のシンボルを探しに行く。シンボルを探すには nm -D <shared library> というコマンドを使用する。


まずは実行バイナリ target/debug/toy-tcpip-rs のシンボルテーブルを表示してみる。

出力を見ると、write 関数が UU write@GLIBC_2.17)、つまり未定義となっており、これはどこか別のライブラリから解決されることを表す。ちなみに @GLIBC_2.17 は「最低でも glibc 2.xx が必要」ということを表す。

nm -D target/debug/toy-tcpip-rs

# --- output ---
                 U abort@GLIBC_2.17
                 U bcmp@GLIBC_2.17
                 U calloc@GLIBC_2.17
                 U close@GLIBC_2.17
                 w __cxa_finalize@GLIBC_2.17
                 w __cxa_thread_atexit_impl@GLIBC_2.18
                 U dl_iterate_phdr@GLIBC_2.17
                 U __errno_location@GLIBC_2.17
                 U fcntl@GLIBC_2.17
                 U free@GLIBC_2.17
                 U fstat64@GLIBC_2.33
                 U getauxval@GLIBC_2.17
                 U getcwd@GLIBC_2.17
                 U getenv@GLIBC_2.17
                 w __gmon_start__
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 U __libc_start_main@GLIBC_2.34
                 U lseek64@GLIBC_2.17
                 U malloc@GLIBC_2.17
                 U memcpy@GLIBC_2.17
                 U memmove@GLIBC_2.17
                 U memset@GLIBC_2.17
                 U mmap64@GLIBC_2.17
                 U mprotect@GLIBC_2.17
                 U munmap@GLIBC_2.17
                 U open64@GLIBC_2.17
                 U pause@GLIBC_2.17
                 U poll@GLIBC_2.17
                 U posix_memalign@GLIBC_2.17
                 U pthread_attr_destroy@GLIBC_2.17
                 U pthread_attr_getguardsize@GLIBC_2.34
                 U pthread_attr_getstack@GLIBC_2.34
                 U pthread_getattr_np@GLIBC_2.32
                 U pthread_key_create@GLIBC_2.34
                 U pthread_key_delete@GLIBC_2.34
                 U pthread_self@GLIBC_2.17
                 U pthread_setspecific@GLIBC_2.34
                 U read@GLIBC_2.17
                 U readlink@GLIBC_2.17
                 U realloc@GLIBC_2.17
                 U realpath@GLIBC_2.17
                 U sigaction@GLIBC_2.17
                 U sigaltstack@GLIBC_2.17
                 U signal@GLIBC_2.17
                 U stat64@GLIBC_2.33
                 w statx@GLIBC_2.28
                 U strlen@GLIBC_2.17
                 U syscall@GLIBC_2.17
                 U sysconf@GLIBC_2.17
                 U _Unwind_Backtrace@GCC_3.3
                 U _Unwind_DeleteException@GCC_3.0
                 U _Unwind_GetDataRelBase@GCC_3.0
                 U _Unwind_GetIP@GCC_3.0
                 U _Unwind_GetIPInfo@GCC_4.2.0
                 U _Unwind_GetLanguageSpecificData@GCC_3.0
                 U _Unwind_GetRegionStart@GCC_3.0
                 U _Unwind_GetTextRelBase@GCC_3.0
                 U _Unwind_RaiseException@GCC_3.0
                 U _Unwind_Resume@GCC_3.0
                 U _Unwind_SetGR@GCC_3.0
                 U _Unwind_SetIP@GCC_3.0
                 # ↓ write はここ ↓
                 U write@GLIBC_2.17
                 U writev@GLIBC_2.17
                 U __xpg_strerror_r@GLIBC_2.17
nukopynukopy

続いて、共有ライブラリである libc のシンボルテーブルを見に行く。libc のパスは ldd コマンドの出力を見れば分かる。

ldd target/debug/toy-tcpip-rs

# --- output ---
        linux-vdso.so.1 (0x0000ede54ecd3000)
        libgcc_s.so.1 => /lib/aarch64-linux-gnu/libgcc_s.so.1 (0x0000ede54ebf0000)
        # ↓ ここ ↓
        libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000ede54ea30000)
        /lib/ld-linux-aarch64.so.1 (0x0000ede54ec96000)

libc (glibc) に対して、nm コマンドを実行してシンボルテーブルを表示してみる。

nm -D /lib/aarch64-linux-gnu/libc.so.6 | grep write

# --- output ---
0000000000090c20 T aio_write@@GLIBC_2.34
0000000000090c20 T aio_write@GLIBC_2.17
0000000000090c20 T aio_write64@@GLIBC_2.34
0000000000090c20 T aio_write64@GLIBC_2.17
00000000000ebf40 T eventfd_write@@GLIBC_2.17
00000000000708a0 W fwrite@@GLIBC_2.17
000000000007b900 T fwrite_unlocked@@GLIBC_2.17
000000000007c9a0 T _IO_do_write@@GLIBC_2.17
000000000007d854 T _IO_file_write@@GLIBC_2.17
00000000000708a0 T _IO_fwrite@@GLIBC_2.17
0000000000076350 T _IO_wdo_write@@GLIBC_2.17
00000000000c6d10 T __libc_pwrite@@GLIBC_PRIVATE
00000000000ec5e0 T process_vm_writev@@GLIBC_2.17
00000000000c6d10 W pwrite@@GLIBC_2.17
00000000000c6d10 W __pwrite64@@GLIBC_2.17
00000000000c6d10 W pwrite64@@GLIBC_2.17
00000000000e8170 T pwritev@@GLIBC_2.17
00000000000e8270 T pwritev2@@GLIBC_2.26
00000000000e8170 T pwritev64@@GLIBC_2.17
00000000000e8270 T pwritev64v2@@GLIBC_2.26
00000000000e1d20 W __write@@GLIBC_2.17
00000000000e1d20 W write@@GLIBC_2.17
00000000000e24a0 T __write_nocancel@@GLIBC_PRIVATE
00000000000eb5e0 W writev@@GLIBC_2.17
nukopynukopy

解説は GPT 5 におまかせ。要点だけ。

  • write は glibc が公開するデフォルト版 GLIBC_2.17 に解決される。
  • W は弱い公開名、実装本体は別名(内部)にあり、同じアドレスに束ねられているのが普通。
  • @GLIBC_PRIVATE はアプリ不可視の内部用(nocancel 系など)。
  • 同一アドレスで @@GLIBC_2.34@GLIBC_2.17 が並ぶのは、1つの実装を複数の ABI 版としてエクスポートして後方互換してるから。

ここまでで Rust の実行バイナリ → libc の write までを追えた。


最高の“標本”が採れたね。これは glibc (libc.so.6) が外部に公開している「write系」API の目録
行ごとの記号バージョンエイリアス関係を噛み砕くよ。


まず記号の読み方

  • 先頭の 16 進数: そのシンボルのオフセット(PIE なのでロード時ベース+この値が実アドレス)
  • T: Text(実装あり)。このライブラリ内に本体がある
  • W: weak(弱い定義 or 弱い別名)。たいてい本体へのエイリアス(歴史的互換や内部実装の都合)
  • @@GLIBC_x.y: その名前の“デフォルト版”(通常アプリが解決するのはコレ)
  • @GLIBC_x.y: 非デフォルト版(古い互換 ABI 等)
  • @GLIBC_PRIVATE: アプリからリンク禁止の内部用(ld が解決しない、libc 内部だけで使う)

ざっくり分類と要点

A. 低レベル write 系(あなたが探してた“本丸”)

000... W __write@@GLIBC_2.17
000... W write@@GLIBC_2.17
000... T __write_nocancel@@GLIBC_PRIVATE
  • writeデフォルト版が GLIBC_2.17
  • W(weak)なのは、実装本体が別名(内部名)にあり、公開名は弱い別名としてエクスポートしているため(glibc の流儀)。
    実体はビルド時には __GI_write などの内部記号に束ねられていることが多い。
  • __write_nocancelスレッドキャンセルポイントを発生させない内部用。libc 内部で自分たちが安全に呼ぶためのフックで、アプリからは見えない(GLIBC_PRIVATE)。

B. ベクタ版/プロセス間版

... W writev@@GLIBC_2.17
... T pwritev@@GLIBC_2.17
... T pwritev2@@GLIBC_2.26
... T pwritev64@@GLIBC_2.17
... T pwritev64v2@@GLIBC_2.26
... T process_vm_writev@@GLIBC_2.17
  • writev: iovec の配列を一度に書く。
  • pwritev/pwritev2: オフセット指定で書く(ファイル位置を動かさない)。v2 は追加フラグ対応(RWF_* など)。
  • process_vm_writev: 別プロセスのアドレス空間に書き込む特殊 API(ptrace 系の代替用途)。

@@GLIBC_2.26 等は 「その機能は glibc 2.26 以降の ABI」 を意味。古い glibc では解決できない。

C. pwrite 系(別名と内部実装)

... T __libc_pwrite@@GLIBC_PRIVATE
... W pwrite@@GLIBC_2.17
... W __pwrite64@@GLIBC_2.17
... W pwrite64@@GLIBC_2.17
  • 公開名の pwrite/pwrite64weak エイリアス、本体は内部名(__libc_pwrite など)にある典型パターン。
  • ...64LFS(Large File Support)の歴史的互換。aarch64 では“中身は最初から 64bit 対応”だが、ABI 名として残っている。

D. stdio(fwrite)と _IO_*

... W fwrite@@GLIBC_2.17
... T fwrite_unlocked@@GLIBC_2.17
... T _IO_fwrite@@GLIBC_2.17
... T _IO_do_write@@GLIBC_2.17
... T _IO_file_write@@GLIBC_2.17
... T _IO_wdo_write@@GLIBC_2.17
  • fwritestdio(バッファ付き)。最終的には write 相当へ落ちるが、_IO 層(glibc の FILE* 実装)が間に入る。
  • fwrite_unlockedロック無し高速版(スレッドセーフ性を自分で担保する前提)。

E. 非同期 I/O / eventfd

... T aio_write@@GLIBC_2.34 / @GLIBC_2.17
... T aio_write64@@GLIBC_2.34 / @GLIBC_2.17
... T eventfd_write@@GLIBC_2.17
  • aio_write*: POSIX AIO(glibc 実装はスレッドベースエミュが多い)。
    **同じアドレスで 2 つの版(@@ と @)**が見えるのは、同一実装に複数の“シンボルバージョン”を割り当てて後方互換してるから。
  • eventfd_write: eventfd カウンタへ書くユーティリティ。

ここから読み取れること(Rust 目線)

  • あなたの Rust バイナリの U write@GLIBC_2.17 は、この write@@GLIBC_2.17(デフォルト版)に解決される。
  • writev も同様。println! 自体は write/writev どちらでもあり得るが、strace -e write,writev で実測が一番早い。
  • weak(W)は**“弱い別名として公開してる”というだけで、使えないわけではない。実体(T を持つ内部名)に正しく束ねられている**。

合わせ技で“呼び出し経路”まで見る

  • 実行時に何が呼ばれたか
    strace -e write,writev ./target/debug/toy-tcpip-rs
  • 動的リンカがどの版に結びつけたか(明示)
    readelf --dyn-syms /lib/aarch64-linux-gnu/libc.so.6 | grep -w ' write'
    write@@GLIBC_2.17 が“デフォルト版”であることが分かる
  • write の実装を逆アセンブル
    objdump -d /lib/aarch64-linux-gnu/libc.so.6 | sed -n '/<write>/,/<.*>:/p' | head -n 40
    → AArch64 なら最終的に svc #0(システムコール発行)が見えるはず

まとめ(要点だけ)

  • write は glibc が公開するデフォルト版 GLIBC_2.17 に解決される。
  • W弱い公開名、実装本体は別名(内部)にあり、同じアドレスに束ねられているのが普通。
  • @GLIBC_PRIVATEアプリ不可視の内部用(nocancel 系など)。
  • 同一アドレスで @@GLIBC_2.34@GLIBC_2.17 が並ぶのは、1つの実装を複数の ABI 版としてエクスポートして後方互換してるから。
nukopynukopy

write システムコールが実際に呼び出されているかを見る

最後に、実行バイナリを strace で追って、write システムコールが呼び出されているところを見る。

Hello, world!\n という文字列は制御文字を含めて 14 byte なので、戻り値として write されたバイト数が返っている。

strace -o strace.write.log -e write target/debug/toy-tcpip-rs
cat strace.write.log

# --- output ---
write(1, "Hello, world!\n", 14)         = 14
+++ exited with 0 +++

全体像はこちら:

strace -o strace.all.log target/debug/toy-tcpip-rs
cat strace.all.log

# --- output ---
execve("target/debug/toy-tcpip-rs", ["target/debug/toy-tcpip-rs"], 0xffffc2ec6e80 /* 23 vars */) = 0
brk(NULL)                               = 0xc283e9df1000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xe08588f13000
faccessat(AT_FDCWD, "/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=23703, ...}) = 0
mmap(NULL, 23703, PROT_READ, MAP_PRIVATE, 3, 0) = 0xe08588f0d000
close(3)                                = 0
openat(AT_FDCWD, "/lib/aarch64-linux-gnu/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=133696, ...}) = 0
mmap(NULL, 263104, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_DENYWRITE, -1, 0) = 0xe08588e99000
mmap(0xe08588ea0000, 197568, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0xe08588ea0000
munmap(0xe08588e99000, 28672)           = 0
munmap(0xe08588ed1000, 33728)           = 0
mprotect(0xe08588ebf000, 65536, PROT_NONE) = 0
mmap(0xe08588ecf000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1f000) = 0xe08588ecf000
close(3)                                = 0
openat(AT_FDCWD, "/lib/aarch64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0\360\206\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1722920, ...}) = 0
mmap(NULL, 1892240, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_DENYWRITE, -1, 0) = 0xe08588cd2000
mmap(0xe08588ce0000, 1826704, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0xe08588ce0000
munmap(0xe08588cd2000, 57344)           = 0
munmap(0xe08588e9e000, 8080)            = 0
mprotect(0xe08588e7a000, 77824, PROT_NONE) = 0
mmap(0xe08588e8d000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19d000) = 0xe08588e8d000
mmap(0xe08588e92000, 49040, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xe08588e92000
close(3)                                = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xe08588f0b000
set_tid_address(0xe08588f0b0f0)         = 7140
set_robust_list(0xe08588f0b100, 24)     = 0
rseq(0xe08588f0b740, 0x20, 0, 0xd428bc00) = 0
mprotect(0xe08588e8d000, 12288, PROT_READ) = 0
mprotect(0xe08588ecf000, 4096, PROT_READ) = 0
mprotect(0xc283ba45d000, 12288, PROT_READ) = 0
mprotect(0xe08588f18000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0xe08588f0d000, 23703)           = 0
ppoll([{fd=0, events=0}, {fd=1, events=0}, {fd=2, events=0}], 3, {tv_sec=0, tv_nsec=0}, NULL, 0) = 0 (Timeout)
rt_sigaction(SIGPIPE, {sa_handler=SIG_IGN, sa_mask=[PIPE], sa_flags=SA_RESTART}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
getrandom("\x23\xf1\xac\x7f\x02\xe3\x68\xee", 8, GRND_NONBLOCK) = 8
brk(NULL)                               = 0xc283e9df1000
brk(0xc283e9e12000)                     = 0xc283e9e12000
openat(AT_FDCWD, "/proc/self/maps", O_RDONLY|O_CLOEXEC) = 3
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
read(3, "c283ba400000-c283ba44d000 r-xp 0"..., 1024) = 1024
read(3, "/lib/aarch64-linux-gnu/libgcc_s."..., 1024) = 1024
read(3, "7000 rw-p 00000000 00:00 0      "..., 1024) = 60
close(3)                                = 0
sched_getaffinity(7140, 32, [0 1 2 3])  = 8
rt_sigaction(SIGSEGV, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
sigaltstack(NULL, {ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=0}) = 0
mmap(NULL, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0xe08588f0e000
mprotect(0xe08588f0e000, 4096, PROT_NONE) = 0
sigaltstack({ss_sp=0xe08588f0f000, ss_flags=0, ss_size=16384}, NULL) = 0
rt_sigaction(SIGSEGV, {sa_handler=0xc283ba424c3c, sa_mask=[], sa_flags=SA_ONSTACK|SA_SIGINFO}, NULL, 8) = 0
rt_sigaction(SIGBUS, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGBUS, {sa_handler=0xc283ba424c3c, sa_mask=[], sa_flags=SA_ONSTACK|SA_SIGINFO}, NULL, 8) = 0
write(1, "Hello, world!\n", 14)         = 14
sigaltstack({ss_sp=NULL, ss_flags=SS_DISABLE, ss_size=16384}, NULL) = 0
munmap(0xe08588f0e000, 20480)           = 0
exit_group(0)                           = ?
+++ exited with 0 +++
nukopynukopy

結論

build-essential の中で Rust プログラムを書くのに最低限必要なのは、gcclibc6-dev

実用上は build-essential を入れておいた方が良い。g++make は各種ツールのビルドに使われていることがあるので、インストールしようとしたときにビルドが走って必要なツールが入ってなくてエラーになる、みたいなことが起きる。

あとソフトウェアのソースコードから何かをビルドするときは make はほぼ必須級。

build-essential

  • dpkg-dev (>= 1.17.11)
    • Debian package development tools
    • .deb を作りたい、.deb を配布したいときに利用。
  • g++ (>= 4:10.2)
    • GNU C++ compiler
    • C++ を書くときに必要。または C++ に依存しているライブラリをビルドするときに必要。
  • gcc (>= 4:10.2)
    • GNU C compiler
  • libc
    • libc6-dev
      • GNU C Library: Development Libraries and Header Files
    • or libc-dev
      • virtual package provided by libc6-dev
  • make
    • utility for directing compilation
    • Makefile でビルドのフローを定義するのに必要。

最後に build-essential の各パッケージのメタデータを確認し、実際に最低限必要なものしか VM に入っていないことを確認する。

dpkg -s dpkg-dev
# dpkg-query: package 'dpkg-dev' is not installed and no information is available
# Use dpkg --info (= dpkg-deb --info) to examine archive files.

dpkg -s g++
# dpkg-query: package 'g++' is not installed and no information is available
# Use dpkg --info (= dpkg-deb --info) to examine archive files.

dpkg -s gcc
# Package: gcc
# Status: install ok installed
# ...
# Architecture: arm64
# Version: 4:13.2.0-7ubuntu1
# Provides: c-compiler
# Depends: cpp (= 4:13.2.0-7ubuntu1), cpp-aarch64-linux-gnu (= 4:13.2.0-7ubuntu1), gcc-13 (>= 13.2.0-11~), gcc-aarch64-linux-gnu (= 4:13.2.0-7ubuntu1)
# Recommends: libc6-dev | libc-dev
# Description: GNU C compiler
# This is the GNU C compiler, a fairly portable optimizing compiler for C.
#  .
#  This is a dependency package providing the default GNU C compiler.

dpkg -s libc6-dev
# Package: libc6-dev
# Status: install ok installed
# ...
# Architecture: arm64
# Version: 2.39-0ubuntu8.5
# Replaces: libc6 (<< 2.38-1ubuntu1)
# Provides: libc-dev (= 2.39-0ubuntu8.5)
# Depends: libc6 (= 2.39-0ubuntu8.5), libc-dev-bin (= 2.39-0ubuntu8.5), linux-libc-dev, libcrypt-dev, rpcsvc-proto
# Description: GNU C Library: Development Libraries and Header Files
#  Contains the symlinks, headers, and object files needed to compile
#  and link programs which use the standard C library.
# Homepage: https://www.gnu.org/software/libc/libc.html
# ...

dpkg -s make
# dpkg-query: package 'make' is not installed and no information is available
# Use dpkg --info (= dpkg-deb --info) to examine archive files.
nukopynukopy

ここまでで Rust の実行バイナリ → libc の write までを追えた。
最後に、アセンブリレベルで syscall が呼ばれているところまで追いたい。