/var/log/study

つまり雑記

ClionでLinuxカーネルのソースコード定義ジャンプを効かせる方法

概要

  • 準備
    • Clionが稼働するLinux環境
      • devcontainerやSSHはNG (Clionがリモート接続にならないようにする)
    • Linuxソースコード
    • Linuxをビルドするためのツール一式
      • 手順で生成する compile_commands.json 内に記載されたコマンドをClionが実行できるようにする
  • 手順
    1. compile_commands.json を用意する
    2. make allyesconfig && make -j $(nproc) && make compile_commands.json
    3. Clion を一度開いて、以下の設定を入れておく
    4. Help > Change Memory Settings で適当にHeapのサイズを上げる
    5. Help > Edit Custom Properties で以下の2つの値を大きくする
      • idea.max.intellisense.filesize
      • idea.max.content.load.filesize
    6. Clionで compile_commands.json を開いて、linuxディレクトリをCompilation database projectとして認識させる
    7. Build タブで linux: finixshed, Compilation database project successfully imported というメッセージが出ることを確認する
    8. (多分) しばらくすると、Clionがコードをキャッシュし始めて、定義ジャンプできるようになる
    9. コードのキャッシュが始まらないと、定義ジャンプできるようにはならない
    10. 最初は定義ジャンプ出来なかったが、いくつかのコードで定義ジャンプを試していたらコードのキャッシュが始まって、完了したら定義ジャンプできるようになった

メモ

Kinesis Advantage 360 の ホットスワップ化に挫折した話

タイトル通りの話ではあるけれども、 Kinesis Advantage 360 のホットスワップ化に自分は挫折した。

こういうのは大抵うまくいった話が書き残されるモノだと思うけど、チャレンジしてみてうまくいかなかった話が書き残されているのも大事かな?と思ったので書き残しておく

モチベーション

  • オンラインMTGをする時にメモを取るなどでタイピングをすると、マイクが打鍵音を拾って困るので静音化したかった
  • どうせキースイッチを入れ替えるならホットスワップ化しておくか、という気持ちになった

やりたかったこと

  • Kinesis Advantage 360 のホットスワップ
  • 元からついていた「Gateron Brown」を「Kailh Box Deep-Sea Silent PRO」 に入れ替え

自分の電子工作のレベル

初心者。
過去にErgoDashというキーボードのキットを買って組み立てたことがある程度。
はんだ付けとか、はんだの除去はそのキットを組み立てた時の経験しかなかった。

自分が挫折したポイント

  • 古いキーを取り除くのがうまくいかなかった
    • 1つのキーごとに以下を繰り返して様子見をしていたが、2個めのキーですでに動作不良を起こしていた
      1. ハンダ吸い取り機とハンダ吸い取り線を使いつつ、古いのを抜去
      2. ホットスワップのソケット取り付け+キーの取り付け
      3. 動作確認
    • 多分、古いキーの抜去の時のハンダの除去が甘くて、基板側を駄目にしたと思われる
      • はんだごての温度が駄目だったのか、ハンダ吸い取り機の使い方が駄目だったのか、ハンダ吸い取り線の使い方が駄目だったのか...
  • 親指周りのキーの抜去 -> ソケット取り付けが異様に難しい
    • 2個めのキーが動作不良になったあとも、一旦それの対処は後回しにして、左手側のキーを全部入れ替えることにした
    • ただ、親指周りのキーの抜去が初心者には?異様に難しく、結局ペンチでもぎ取るに近いかたちでの抜去になった
    • その後、基板に残ったハンダをきれいに取り除いたつもりになっても今度はホットスワップ化のソケットが取り付けられない...

というあたりで、合計6時間くらい使って、左手側の状態が動作不良+中途半端な状態になって、挫折した。

多分、ハンダ除去に熟練した人がやったらもっと異なる結果になったと思う。

やってみた感触

タイプしたキーに対して、正しく文字入力されない点はさておき、親指周り以外はキースイッチを取り付けて、タイピングできるモックアップ程度にはなったので、感触を書いておく

  • ホットスワップ化に関して
    • Kinesis Advantage 360 を利用していくうえで、キーのホットスワップはやらないほうが良いのだと思う
      1. 湾曲してる面で、キーを正しくソケットに刺していくしていくのが大変。
        • ホットスワップ化していないよりキー入れかえは楽になるのだろうけど、結局、キーボードを開けて基板と筐体の間を見つつ、入れ替えていく必要になる
      2. キーキャップを取り外そうとすると、キーごと抜ける
        • ホットスワップとはそういうものなのだ、と言われたらそうなのかもしれないが、キーをソケットに指すのが大変なため、キーキャップを外す程度でキーが抜けるとまぁまぁ面倒そう
  • 静音化に関して
    • キーに対して正しく入力されないという話はさておき、キースイッチを取り替えることで静かになった
      • kinesisの筐体の作り上、キースイッチを交換しても静かにならないのでは?という懸念は払拭できた

まとめ

  • 電子工作初心者がKinesis Advantage 360のホットスワップ化とキースイッチの入れ替えをやるのは多分ハードルが高い
  • ホットスワップ化すると、後のメンテナンス性で面倒事が増えそう
  • 静音化のキーに入れ替えるとちゃんと静音化される (筐体が起因で静かにならない、ということはない)

その他

準備

Kinesis Advantage 360 について

駄目にしてしまったが、無いと困るので、以下から追加でもう一台注文をしようとしている。
普通に注文すると、USにしか配送してくれなさそうに見えるが、「Our store only supports US customers. If you are located outside of the United States click here.」 のリンクから注文を受け付けてくれそう。
送料は+100$とのこと

あと、適当にぐぐると15%オフのクーポンが出てくる。(これは期間限定なのか、否かはわからない)

kinesis-ergo.com

amazonからでも購入できそうだが、販売元がよくわからないので、本家から買うのがいいのかamazonを通して買うのがいいのかはなんともいえなさそう。

qemu上に用意したNW接続設定の無いFreeBSD4.10とホスト側のLinuxと適当にファイルを受け渡しする方法のメモ

リンカローダ実践開発テクニックをまだやっている。

今までは特に深いこと考えずFreeBSDであることだけ合わせて写経していたのだけど、 8章からは、書籍の内容の都合から、FreeBSD 4.10+i386を用意して試したくなったのでそのメモ

下記の一連の操作をできるようになった後に気づいたけど、素直にゲスト側をNWに接続してSSHで受け渡したら良かったのだと思うが、供養ということでメモとして残す。

ホスト側の情報

$ uname -a
Linux sh-amd64-lnx-ws-01 6.12.19 #1-NixOS SMP PREEMPT_DYNAMIC Thu Mar 13 12:02:20 UTC 2025 x86_64 GNU/Linux
$ cat /etc/os-release
ANSI_COLOR="1;34"
BUG_REPORT_URL="https://github.com/NixOS/nixpkgs/issues"
BUILD_ID="24.11.20250323.f0946fa"
CPE_NAME="cpe:/o:nixos:nixos:24.11"
DEFAULT_HOSTNAME=nixos
DOCUMENTATION_URL="https://nixos.org/learn.html"
HOME_URL="https://nixos.org/"
ID=nixos
ID_LIKE=""
IMAGE_ID=""
IMAGE_VERSION=""
LOGO="nix-snowflake"
NAME=NixOS
PRETTY_NAME="NixOS 24.11 (Vicuna)"
SUPPORT_END="2025-06-30"
SUPPORT_URL="https://nixos.org/community.html"
VARIANT=""
VARIANT_ID=""
VENDOR_NAME=NixOS
VENDOR_URL="https://nixos.org/"
VERSION="24.11 (Vicuna)"
VERSION_CODENAME=vicuna
VERSION_ID="24.11

ゲスト側の情報

# uname -a
FreeBSD  4.10-RELEASE FreeBSD 4.10-RELEASE #0: Tue May 25 22:47:12 GMT 2004     root@perseus.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC  i386

qemu上にFreeBSD 4.10 を用意する

特に難しいことは無く、 http://ftp-archive.freebsd.org/pub/FreeBSD-Archive/old-releases/i386/ISO-IMAGES/4.10/4.10-RELEASE-i386-miniinst.iso を使った。
書籍の内容的に対して必要なファイルは揃っていた。

あとは、qcow2を利用してディスクを作って、qemu-system-i386を利用してisoとディスクを利用して... と言った感じでqemuを起こすと、FreeBSDインストーラーが走るのでインストーラーに従う。(NWの設定はインストーラーの質問でoffにした)

qemu-img create -f qcow2 $(pwd)/freebsd-4.10-miniinst.qcow2 30G
qemu-system-i386 -m 1024 -boot order=d -cdrom ./4.10-RELEASE-i386-miniinst.iso ./freebsd-4.10-miniinst.qcow2

FreeBSD用のディスクからファイルを引き出す

kvm.hatenadiary.org

上記を参考に、qcow2をマウントして、ファイルをコピーしてきたらOK。ただし、read-onlyでしかマウントできなかったので、書き込む方法は別に検討する必要がある

FreeBSDをインストールしたイメージを /dev/nbd0 として見せるようにして

sudo modprobe nbd #読み込まれていなければnbdモジュールを読み込む
sudo qemu-nbd -c /dev/nbd0 $(pwd)/freebsd-4.10-miniinst.qcow2 # 作成したディスクイメージを /dev/nbd0 として見せる
sudo lsblk /dev/nbd0 # パーティションの情報を見る
NAME     MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
nbd0      43:0    0  30G  0 disk
└─nbd0p1  43:1    0  30G  0 part
sudo mount -r -o ufstype=44bsd /dev/nbd0p1 $(pwd)/mnt # 参照した情報からパーティションを44bsdでマウントする
cd mnt/
ls
bin   cdrom   COPYRIGHT  dist  home    kernel.GENERIC  modules  root  stand  tmp  var
boot  compat  dev        etc   kernel  mnt             proc     sbin  sys    usr

あとは、ほしいファイルをマウントしたポイントからLinux側に拾ってきたらOK

FreeBSDに対してファイルを送る

4.10-RELEASE-i386-miniinst.iso で追加パッケージなしで設定だけ弄っていい感じにやる方法が無いか悩んだ結果、自分の答えとしては以下のようになった。

  1. ゲスト側のシリアルコンソールをホスト側に見せる
  2. ホストに見せているシリアルコンソールはログインしてshellが待ち受けている状態にしておく
  3. ホスト側からゲスト側に対して、ファイルを1行ずつ書き込むコマンドを流し込む

シリアルコンソールに繋いだscreenでファイルの内容を雑にペーストすると、ペーストが早すぎるのか、ゲスト側のキューが溢れてペースとした内容が受け取れなかった。

FreeBSD側での変更

/boot/loader.conf に以下を追記

 # cat /boot/loader.conf
 # -- sysinstall generated deltas -- #
 userconfig_script_load="YES"
+ console="comconsole"
+ comconsole_speed="115200"

/etc/ttys の以下を変更

- ttyd0   "/usr/libexec/getty std.9600" dialup  off secure
+ ttyd0   "/usr/libexec/getty std.115200" vt100 on secure
- ttyd1   "/usr/libexec/getty std.9600" dialup  off secure
+ ttyd1   "/usr/libexec/getty std.115200" vt100  on secure
- ttyd2   "/usr/libexec/getty std.9600" dialup  off secure
+ ttyd2   "/usr/libexec/getty std.115200" vt100  on secure
- ttyd3   "/usr/libexec/getty std.9600" dialup  off secure
+ ttyd3   "/usr/libexec/getty std.115200" vt100  on secure

ゲスト側での準備

FreeBSDVMのコンソール出力を pty に飛ばすようにする

qemu-system-i386 -m 1024 -boot order=d ./freebsd-4.10-miniinst.qcow2 -serial pty

でもって、screenコマンドを利用して、一旦FreeBSDにログインする
(ログインのプロンプトが出てくるはずなのでログインする)

sudo screen /dev/pts/1 115200

ファイルの中身の転送

qemuを起動したときに、ptyでシリアルコンソールを見れるようにしたので、 あとは /dev/pts/${m} に対して、ホスト側のファイルを1行ずつechoしてファイルに追記するコマンドを流し込むだけ。

つまり、/dev/pts/${n} に対して、ファイルの行数分だけ echo '${str}' >> hoge.txt\n が転送されるようなことを行うという話。

以下のスクリプトはsample.cというC言語のファイルの中身を1行ずつ飛ばすために、自分が困らない程度のケアが入っている。具体的には ゲスト側で ! を評価しないようにするためにエスケープ処理と、 * を展開して評価しないように set -f している。

cat sample.c | while IFS= read -r l; do
    set -f;
    l=$(echo "$l" | sed 's/!/\\!/g');
    echo 'echo '"'"$l"'"' >> sample.c' > /dev/pts/1;
    sleep 0.1;
done

リンカ・ローダ実践開発テクニック 6章のoverwrite.cをFreeBSD14とamd64で試す備忘録

今読んで試しているのは「リンカ・ローダ実践開発テクニック」という書籍。
6章にリンカスクリプトを弄って、普通ならできないメモリの弄り方を実現するCのサンプルコードが動かなかったので、その原因と対処方法の備忘録。

勉強中なので間違った内容が書いてあったらすみません。また、そもそも書籍の6章自体が「実験」と言っているため、実用的なテクではないのに注意してください。

実行環境

$ uname -a
FreeBSD freebsd14.localdomain 14.2-RELEASE-p1 FreeBSD 14.2-RELEASE-p1 GENERIC amd64

対象のサンプルコード

書籍内のサンプルコードが以下。
普通に gcc overwrite.c -o errwriteコンパイルして、 errwrite を実行すると、 Segmentation fault が出る。
memcpyを呼び出している箇所で、func1が .textセグメントに乗っていて、roな領域に書き込もうとするため。

// overwrite.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int value;
char buffer[100];

void func1() { value += 1; }
void func2() { value += 2; }
void func3() { value += 4; }

int main() {
  value = 0; func1(); func2(); func3();
  printf("value = %d\n", value);
  memcpy(func1, func2, (char *)func3 - (char *)func2);
  memcpy(buffer, func3, (char *)main - (char *)func3);
  value = 0; func1(); func2(); ((void(*)())buffer)();
  printf("value = %d\n", value);
  exit (0);
}

これに対して以下のsample.ldsを使って、各セクションをrweな場所に配置することで、overwrite.cを実行可能にするサンプルコードが掲載されている。
コンパイルの仕方としては、 gcc overwrite.c -o overwrite -Wall -T sample.lds -L/usr/lib/

/* sample.lds */
SECTIONS
{
  . = 0x08048000 + SIZEOF_HEADERS;
  .text    : { *( .text)   }
  .rodata  : { *( .rodata) }
  .data    : { *( .data)   }
  .bss     : { *( .bss)    }
}

しかし、これでもamd64な環境ではSegmentation Faultが発生する。

理由

sample.ldsを利用すると、func1, bufferの両方がrwxなメモリに配置される。これは書籍のサンプルの狙い通りのはず。memcpyの箇所も通り抜けることができる。

後述する調査で、エラーが発生しているのは、((void(*)())buffer)(); の内部であることがわかった。
amd64環境でoverwrite.cをコンパイルすると、func1,func2,func3のいずれもvalue変数への参照がインストラクションポインタからの相対位置となる様。言い換えると、func1,func2,func3ではvalueの位置を絶対参照していない。なので、func3の内容をbufferにコピーすると、もともとのvalue変数とは全然関係ないメモリの位置を参照する命令が出来上がり、実行するとセグフォが出ていた。

実験の趣旨 (リンカスクリプトを利用すると特定のメモリ領域を弄ることができるようになる) という点は達成できているが、セグフォを解消して実行できるようにしたいので、調べて試した。

調査方法

サンプルコードを以下の様に書き換えて、gcc -g3 overwrite.mod.c -o overwrite.mod -Wall -T sample.lds -L/usr/lib/コンパイルして、gdbで動作過程を調べられるようにする。

// overwrite.mod.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int value;
char buffer[100];

void func1() { value += 1; }
void func2() { value += 2; }
void func3() { value += 4; }

int main() {
  printf("func1\t%#x\n", func1);
  printf("func2\t%#x\n", func2);
  printf("func3\t%#x\n", func3);
  printf("main\t%#x\n", main);
  printf("buffe\t%#x\n", buffer);
  value = 0;
  func1();
  func2();
  func3();
  printf("value1 = %d\n", value);
  memcpy(func1, func2, (char *)func3 - (char *)func2);
  memcpy(buffer, func3, (char *)main - (char *)func3);
  value = 0;
  func1();
  printf("value2_1 = %d\n", value);
  func2();
  printf("value2_2 = %d\n", value);
  ((void(*)())buffer)();
  printf("value2_3 = %d\n", value);
  exit (0);
}

mainにブレークポイントを打って、ステップ実行し、memcpyが完了したあとの、func1とfunc2の内容をdisassembleする。

そうするとfunc2では value(0x8048b20) が正しく参照できていることに対して、func1ではvalueを参照したいはずのところで、valueが参照できていない(0x8048b0a を参照している) のがわかる。 (なので、 printf("value2_1 = %d\n", value); の行でvalueの中身を見ても、正しく加算されていない。)

メモ: 0x00000000080482cc <+4>: mov 0x84e(%rip),%eax # 0x8048b20 <value> については次の行の、 0x00000000080482d2 + 0x84e0x8048b20 になっている

23        memcpy(func1, func2, (char *)func3 - (char *)func2);
(gdb) n
24        memcpy(buffer, func3, (char *)main - (char *)func3);
(gdb) n
25        value = 0;
(gdb) disass func2
Dump of assembler code for function func2:
   0x00000000080482c8 <+0>:     push   %rbp
   0x00000000080482c9 <+1>:     mov    %rsp,%rbp
   0x00000000080482cc <+4>:     mov    0x84e(%rip),%eax        # 0x8048b20 <value>
   0x00000000080482d2 <+10>:    add    $0x2,%eax
   0x00000000080482d5 <+13>:    mov    %eax,0x845(%rip)        # 0x8048b20 <value>
   0x00000000080482db <+19>:    nop
   0x00000000080482dc <+20>:    pop    %rbp
   0x00000000080482dd <+21>:    ret
End of assembler dump.
(gdb) disass func1
Dump of assembler code for function func1:
   0x00000000080482b2 <+0>:     push   %rbp
   0x00000000080482b3 <+1>:     mov    %rsp,%rbp
   0x00000000080482b6 <+4>:     mov    0x84e(%rip),%eax        # 0x8048b0a
   0x00000000080482bc <+10>:    add    $0x2,%eax
   0x00000000080482bf <+13>:    mov    %eax,0x845(%rip)        # 0x8048b0a
   0x00000000080482c5 <+19>:    nop
   0x00000000080482c6 <+20>:    pop    %rbp
   0x00000000080482c7 <+21>:    ret
End of assembler dump.

同様に、func3とbufferをdisassembleすると、bufferでも同じことが起きているのがわかる。

(gdb) disass func3
Dump of assembler code for function func3:
   0x00000000080482de <+0>:     push   %rbp
   0x00000000080482df <+1>:     mov    %rsp,%rbp
   0x00000000080482e2 <+4>:     mov    0x838(%rip),%eax        # 0x8048b20 <value>
   0x00000000080482e8 <+10>:    add    $0x4,%eax
   0x00000000080482eb <+13>:    mov    %eax,0x82f(%rip)        # 0x8048b20 <value>
   0x00000000080482f1 <+19>:    nop
   0x00000000080482f2 <+20>:    pop    %rbp
   0x00000000080482f3 <+21>:    ret
End of assembler dump.
(gdb) disass buffer
Dump of assembler code for function buffer:
   0x0000000008048b40 <+0>:     push   %rbp
   0x0000000008048b41 <+1>:     mov    %rsp,%rbp
   0x0000000008048b44 <+4>:     mov    0x838(%rip),%eax        # 0x8049382
   0x0000000008048b4a <+10>:    add    $0x4,%eax
   0x0000000008048b4d <+13>:    mov    %eax,0x82f(%rip)        # 0x8049382
   0x0000000008048b53 <+19>:    nop
   0x0000000008048b54 <+20>:    pop    %rbp
   0x0000000008048b55 <+21>:    ret
   0x0000000008048b56 <+22>:    add    %al,(%rax)

特に、bufferの方の 0x8049382 に関しては、objdumpした結果の領域外になっている。 以下がobjdumpの結果。

$ /usr/local/bin/objdump -h -p overwrite.mod

overwrite.mod:     file format elf64-x86-64-freebsd

Program Header:
    PHDR off    0x0000000000000040 vaddr 0x0000000008048040 paddr 0x0000000008048040 align 2**3
         filesz 0x0000000000000188 memsz 0x0000000000000188 flags r--
  INTERP off    0x0000000000000505 vaddr 0x0000000008048505 paddr 0x0000000008048505 align 2**0
         filesz 0x0000000000000015 memsz 0x0000000000000015 flags r--
    LOAD off    0x0000000000000000 vaddr 0x0000000008048000 paddr 0x0000000008048000 align 2**12
         filesz 0x0000000000000b00 memsz 0x0000000000000ba4 flags rwx
 DYNAMIC off    0x00000000000008d8 vaddr 0x00000000080488d8 paddr 0x00000000080488d8 align 2**3
         filesz 0x00000000000001d0 memsz 0x00000000000001d0 flags rw-
    NOTE off    0x00000000000007ec vaddr 0x00000000080487ec paddr 0x00000000080487ec align 2**2
         filesz 0x0000000000000048 memsz 0x0000000000000048 flags r--
EH_FRAME off    0x0000000000000738 vaddr 0x0000000008048738 paddr 0x0000000008048738 align 2**2
         filesz 0x0000000000000014 memsz 0x0000000000000014 flags r--
   STACK off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**4
         filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-
...

余談

memcpyしたあとにfunc1にstep inしても元の関数の中身しか見れない。デバッグ情報にfunc1はそういうものと記録されているのだと思うが未調査。

(gdb) n
26        func1();
(gdb) s
func1 () at overwrite.mod.c:8
8       void func1() { value += 1; }
(gdb) n
main () at overwrite.mod.c:27
27        func2();
(gdb) s
func2 () at overwrite.mod.c:9
9       void func2() { value += 2; }
(gdb) n
main () at overwrite.mod.c:28
28        ((void(*)())buffer)();
(gdb) n

Program received signal SIGSEGV, Segmentation fault.
Address not mapped to object.
0x0000000008048b44 in buffer ()

対処方法1 (関数のシグネチャを変える)

そもそも、valueがグローバルな変数であるために、func2もfunc3もインストラクションポインタからの相対位を利用して解決しているのがSegmentatil Faultの理由なので、大人しく、func1, func2, func3がvalueを引数に取るように変更したら問題なく動く。

// overwrite.mod2.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int value;
char buffer[100];

void func1(int *value) { *value += 1; }
void func2(int *value) { *value += 2; }
void func3(int *value) { *value += 4; }

int main() {
  printf("func1\t%#x\n", func1);
  printf("func2\t%#x\n", func2);
  printf("func3\t%#x\n", func3);
  printf("main\t%#x\n", main);
  printf("buffe\t%#x\n", buffer);
  value = 0;
  func1(&value);
  printf("value1_1 = %d\n", value);
  func2(&value);
  printf("value1_2 = %d\n", value);
  func3(&value);
  printf("value1_3 = %d\n", value);
  memcpy(func1, func2, (char *)func3 - (char *)func2);
  memcpy(buffer, func3, (char *)main - (char *)func3);
  value = 0;
  func1(&value);
  printf("value2_1 = %d\n", value);
  func2(&value);
  printf("value2_2 = %d\n", value);
  ((void(*)())buffer)(&value);
  printf("value2_3 = %d\n", value);
  exit (0);
}

対処方法2 (memcpyしたあとに更にメモリの内容を書き換える)

関数のシグネチャを変えたら動くが、valueグローバル変数のままでコードを動かしたいので、こねくり回した結果が以下。
func1に関しては、func2の内容をコピーする前にfunc1の関数の内容の一部を対比しておいて、memcpyしたあと、元の位置にfunc1の内容を戻してやる。
bufferに関しては、もともとは空なので、gdbでbufferにfunc3の内容をコピーしたあと、valueの位置と手打ちでメモリの内容を書き換える。
その際に、bufferのメモリの位置とvalueのメモリの位置関係の都合が悪かったので、bufferの変数宣言とvalueの変数宣言の位置を入れ替えている。

// overwrite.mod3.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char buffer[100];
int value;
char bkup1[6];
char bkup2[6];

void func1() { value += 1; }
void func2() { value += 2; }
void func3() { value += 4; }

int main() {
  printf("func1\t%#x\n", func1);
  printf("func2\t%#x\n", func2);
  printf("func3\t%#x\n", func3);
  printf("main\t%#x\n", main);
  printf("buffe\t%#x\n", buffer);
  value = 0; func1(); func2(); func3();
  printf("value1 = %d\n", value);
  memcpy(bkup1, func1 + 4, 6); // disassemble の結果から、func1の始点から +4 から 6byteがvalueへの参照
  memcpy(bkup2, func1 + 13, 6); // disassemble の結果から、func1の始点から +13 から 6byteがvalueへの参照
  memcpy(func1, func2, (char *)func3 - (char *)func2);
  memcpy(func1+4, bkup1, 6);
  memcpy(func1+13, bkup2, 6);

  memcpy(buffer, func3, (char *)main - (char *)func3);
  buffer[3 + 3] = 0x5A;
  buffer[3 + 4] = 0;
  memcpy(bkup1, buffer + 4, 6);
  buffer[12 + 3] = 0x51;
  buffer[12 + 4] = 0;

  value = 0;
  func1();
  printf("value2_1 = %d\n", value);
  func2();
  printf("value2_2 = %d\n", value);
  ((void(*)())buffer)();
  printf("value2_3 = %d\n", value);
  exit (0);
}

buffer に差し込んでいる処理の内容は、func1を弄る際にbkup1とbkup2に退避した内容を参考に、bufferのアドレスを元に計算して手打ちした。

(gdb) p/x bkup1
$3 = {0x8b, 0x5, 0x28, 0xa, 0x0, 0x0}
(gdb) p/x bkup2
$4 = {0x89, 0x5, 0x1f, 0xa, 0x0, 0x0}

実行結果

特に面白みは無いけど、問題なく実行できる

$ ./overwrite
func1   0x80482b2
func2   0x80482c8
func3   0x80482de
main    0x80482f4
buffe   0x8048c80
value1 = 7
value2_1 = 2
value2_2 = 4
value2_3 = 8

対処方法3

他にも色々ありそう。結局、valueという変数への参照方法が問題なので、参照方法を変える方法があればなんとかなると思う。 自分は対処方法1と対処方法2を見つけて満足しているし、あまりいい方法が思いつかないので、ここらへんで切り上げる。

参考

ディジタル回路設計とコンピュータアーキテクチャの4章をLinux上で試すメモ

ディジタル回路設計とコンピュータアーキテクチャの4章には、SystemVerilogとHDLのサンプルコードが掲載されているのだけれども、ぱっと見た限り、具体的にどのように試すか?が書かれていないのでメモ
(もしかしたらAppendixに掲載されているなどあるかもしれないが、教科書的な用途を考えると具体的な方法が調達できるFPGAによって異なる、などの事情で踏み込んでいないのかもしれない。)

Linux上で試すにあたり必要なツールは以下の2つ。パッケージマネージャーかなにかでインストールしたらOK

  • iverilog
  • gtkwave

上記の2ツールをインストールした後以下の流れで試せる

  • 4章1節に書いてあるsillyfunctionの内容を sillyfunction.v として保存
  • 4章9節に飛んで、例4.37のテストベンチを sillyfunction_test.v として保存
    • sillyfunction_test.vinitial begin の下に以下の2行を追記
module testbench1();
...
    initial begin
        $dumpfile("sillyfunction_test.vcd");
        $dumpvars(1, testbench1);
...
  • iverilog -g2012 -o sillyfunction_test.out sillyfunction.v sillyfunction_test.v
    • sillyfunction_test.out が生成される
  • vvp sillifunction_test.out
    • sillyfunction_test.vcd が生成される
  • gtkwave で sillyfunction_test.vcd を開く
    • SST というタブに、 testbench1 というのがあるはずなのでクリック
      • SST の下を見ると、 reg | a reg | b などが出てくるので全て選択して、右下のAppendをクリック
        • すると、テストベンチに対応した波形が出てくる

参考

NixOS/nixpkgs でビルドに失敗しているパッケージを直してPRを出したかった備忘録

ここ最近nixpkgsに登録されているVagrantが壊れている状態で、NixOS 24.11がリリースされた直後も直る雰囲気が (2024/12/07あたりまで) ありませんでした。

重い腰を上げて 自分が手元でVagrantのビルドを直している内に修正 兼 verupのPRバックポートのPRが取り込まれたので、そのうちNixOS 24.11以降では無事にインストールして動作させられるようになるでしょう。

なので、自分はPRを出すところまではいかなかったのですが、動くところまで修正自体はでき、直す過程を書き残しておくことは意義があると思ったので書き残しておきます。

個人的に、nixのパッケージをビルドするのは初めての試みかつ、雰囲気でやっているので、より良い手順を知っている方がいたらぜひ教えてほしいです。

この記事について

この記事はNix Advent Calendar 2024 の9日の記事 兼 FUJITSU Advent Calendar 2024 の記事です。

NixOS Advent Calenderの8日目の記事は@satleriさんのNixOSを使い始めた でした。
WiFiあたりがつまりどころなのは共感します。自分も今のPCのWiFiのDriverがNixOS 24.11 の標準kernelには入っていないので、kernelのverを上げました。

FUJITSU Advent Calendar 2024の8日目の記事は@ihgsさんのなにか書くでした。
この記事執筆時点でまだ記事が上がってないので頑張ってください

直す過程について

まず「壊れている」、「直した」と言っても本当か?という話があると思うので、壊れていた状態と直してインストールしたあとのVagrantの動作ログはAppendixとして末尾に載せてあります。 壊れているというIssue もあったので載せておきます。

では、実際に自分が手元でnixpkgsのVagrantビルドが壊れているのを直した過程を書いていきます。

直す対象の探し方

nixで利用できるパッケージはnixpkgs に集められていて、実体はリポジトリ内のpkgsディレクトリ配下に色々とまとめられています。pkgs内でのカテゴライズを自分はよく知りませんが、それっぽいカテゴリのディレクトリになければ by-name というディレクトリ配下にパッケージがあるでしょう。Vagrantpkgs/by-name/va/vagrant の中にありました。

ちなみに、ビルドできないパッケージにはbrokenのフラグが建てられます。

とりあえずビルドしてみる

手元にnixpkgsをcloneしてきます。 --shallow にしないと大量のcommitのログが降ってくると思うので、shallowにしておきました。

ghq get --shallow https://github.com/NixOS/nixpkgs.git

cloneしてきたディレクトリに入って、ビルドをしてみます。

cd nixpkgs
# .#ビルドしたいパッケージ名を指定する。今回はvagrant
nix build .#vagrant

当然なのですが、当時の時点でVagrantはbrokenフラグが立っていることに加えて、フリーのパッケージでは無いので、設定なしにビルドを実行すると色々と言われます。 エラーログは割愛しますが、ログに言われている通り2個の環境変数を設定してあげることにします。

# brokenフラグが立っているパッケージをビルドできるようにする
export NIXPKGS_ALLOW_BROKEN=1
# フリーではないパッケージをビルドできるようにする
export NIXPKGS_ALLOW_UNFREE=1

上記の2環境変数を入れたところで、パッケージが壊れている事自体は変わらないので、ビルドには失敗しますが、ひとまずビルドを走らせることができるところまではたどり着きました。

修正の流れについて

ここから先の直し方は、直したいパッケージに依存するところが大きいと思います。 今回の場合だとvagrantvagrant-libvirt あたりでgem の依存関係が競合しているのが問題っぽいので、それを直していきました。

修正のあたりをつける

nixpkgs内のvagrantディレクトリを見ると色々と入っているのですが、まず大事そうなのは、package.nix だろうとあたりをつけて見てみました。

packages.nix を見ると、以下のように

you must use bundix to generate a new gemset.nix in the Vagrant source.

bundixを利用して、gemset.nix を生成しろとあるので、bundixの使い方を覗きに行きます。

bundixの使い方

bundix のREADME

I recommend first reading the nixpkgs manual entry for Ruby as this README might become outdated, it's a short read right now, so you won't regret it.

と書いてあるので nixpkgs manual entry for Ruby を読みに行きました。 複雑なパッケージングをしていると思うのでやむを得ないと思いますが、若干のたらい回され感がある

Using an existing Gemfile の節を読むと、それらしきことが書いてあり、bundixのREADMEと書いてあることは異なりますが、 bundlerを利用してlockファイルを作ったあと、bundixを実行するとgemset.nixが手に入りそうです。

gemset.nixの生成

bunlderを使いGemfileからGemfile.lockを生成し、bundixでGemfile.lockからgemset.nixを生成できることはわかりました。また、nixpkgs内のvagrant には、gemset.nix と gemset_libvirt.nixがあるので、vagrantリポジトリと、vagrant-libvirtの両方からgemset.nixを生成しつつ、流用しつつ、パッチを当てていけば良さそうなことも推測がついてきました。

ということで、nixを利用して、bundlerとbundixをインストールしつつ、vagrantvagrant-libvirtをクローンして、gemsetを生成します。

ghq get https://github.com/hashicorp/vagrant.git
cd vagrant
git fetch origin v2.4.3:v2.4.3
git checkout v2.4.3
budle lock
bundix
ghq get https://github.com/vagrant-libvirt/vagrant-libvirt.git
cd vagrant-libvirt
git fetch origin 0.12.2:0.12.2
git checkout 0.12.2
budle lock
bundix

雰囲気でビルドしてみる

以下の流れで準備しつつビルドします。

  • package.nix を vagrantを v2.4.3 に変更
    • hash値も変更 (後述)
  • vagrantから生成したgetmset.nix を nixpkgs/pkgs/by-name/va/vagrant/gemset.nix にコピー
  • vagrant-lib-vritから生成したgemset.nix を nixpkgs/pkgs/by-name/va/vagrant/gemset_libvirt.nix にコピー
  • nix build .#vagrant --impure でビルド

すると、なんと、1度ではうまくいかないです😇

まず、https://github.com/hashicorp/vagrant/archive/refs/tags/v2.4.3.tar.gz でダウンロードできるはずファイルのhash値でfailするのではないかと思います。nixコマンドのissue にも存在するのですが、サブコマンドによって生成されるハッシュ値が変わってしまうことがあるようです。

自分の環境では、以下の様に異なりました。

対象のverのtar.gzをダウンロードしておく。

[shouhei@nixos:~/src/github.com/NixOS/nixpkgs]$ wget https://github.com/hashicorp/vagrant/archive/refs/tags/v2.4.3.tar.gz

不正な値としてfailedが出る方

[shouhei@nixos:~/src/github.com/NixOS/nixpkgs]$ nix hash path ./v2.4.3.tar.gz
sha256-AWjlG0D0R4GPBKPGke9FTiZ9l6jtFuHgvy+0zYyv0vc=

ビルドの過程のダウンロードでhash値がマッチして先に進める方の値

[shouhei@nixos:~/src/github.com/NixOS/nixpkgs]$ sha256sum ./v2.4.3.tar.gz
65059d4825799412fc5d49cebc2170240164fadc37d2ada54531d1f77a9e6762  ./v2.4.3.tar.gz

[shouhei@nixos:~/src/github.com/NixOS/nixpkgs]$ nix hash convert --hash-algo sha256 65059d4825799412fc5d49cebc2170240164fadc37d2ada54531d1f77a9e6762
sha256-ZQWdSCV5lBL8XUnOvCFwJAFk+tw30q2lRTHR93qeZ2I=

tar.gzのhash値でfailedになる事象を超えたあとは、エラーログを見つつ、gemset.nixとgemset_libvirt.nixのgemでverがミスマッチを起こしているものを https://rubygems.org/ で検索し、適切と思われるバージョンとhashに直していきます(おそらくbundl lockとbundixのより良い実行方法があるのだと思います)。このときgemset.nixとgemset_libvirt.nix内に両方あるgemについてはverとhash値を合わせました。

また、既存のビルドの失敗結果が残っているとうまくいかないことがあるようなので、雑にやるならば nix-collect-garbage -d を実行するとうまく行くみたいです。

ということで、gemのverをちょこちょこ手直ししつつ、何度かビルドをリトライしていくと直せるはずです。

ビルドできたら

無事ビルドが通ったら以下でビルドの成果物を試すことができます

nix profile install .#vagrant --impure

動作確認が終えたら

CONTRIBUTING.md を読みつつ、nixpkgsに対してPRを送るのが良いのでしょう。
今回の場合はすでに修正済みなのでやりませんでしたが。

答え合わせ

前述しましたが、自分が試した修正に似た内容のPRが取り込まれており、以下の様に直すのが正解のようです。

github.com

まとめ

壊れているnixpkgs内のパッケージを直す試みを紹介しました。
Rubyのプロジェクト固有の話が半分くらい、nixpkgs内でのビルドの一般的な話を半分くらいできたと思いますので、なにかの役に立てば幸いです。

おわりに

この記事はNix Advent Calendar 2024 の9日の記事 兼 FUJITSU Advent Calendar 2024 の記事でした。

Nix Advent Calendar 2024の10日目については 、この記事を書いている段階では不在です😭 だれか書いてください!
11日目は@482Fさんの「プロキシ環境下での NixOS-WSL」になります。 プロキシ環境下は何かとつまりごとが多いかと思うので気になります!

FUJITSU Advent Calendar 2024の10日目は@earth429さんのCloud Digital Leader 受験記です。 Google Cloudはそんな認定資格も出しているんですね。

Appendix (インストール失敗のログと、手直し後に動いているログ)

最近の時点ではVagrantのインストールがうまくいかなかったログ

[shouhei@nixos:~/.home]$ nix-shell -p nix-info --run "nix-info -m"
 - system: `"x86_64-linux"`
 - host os: `Linux 6.11.10, NixOS, 24.11 (Vicuna), 24.11.20241205.4dc2fc4`
 - multi-user?: `yes`
 - sandbox: `yes`
 - version: `nix-env (Nix) 2.24.10`
 - nixpkgs: `/nix/store/5wvrlpfrwlmg24ywkybvidyzcvki6bwx-source`
[shouhei@nixos:~/.home]$ nix run nixpkgs#home-manager -- switch --flake .#myHome
warning: Git tree '/home/shouhei/.home' is dirty
error: builder for '/nix/store/pzhy4dln2zzixq04cdz4nq3lld1w8ysn-vagrant-2.4.1.drv' failed with exit code 1;
       last 25 log lines:
       > /nix/store/3613ljbf4wmc94h9pq4lj0ym6dis4l9y-vagrant-2.4.1/lib/ruby/gems/3.3.0/gems/vagrant-2.4.1/scripts/sign.sh: interpreter directive changed from "#!/bin/bash" to "/nix/store/p6k7xp1lsfmbdd731mlglrdj2d66mr82-bash-5.2p37/bin/bash"
       > /nix/store/3613ljbf4wmc94h9pq4lj0ym6dis4l9y-vagrant-2.4.1/lib/ruby/gems/3.3.0/gems/vagrant-2.4.1/scripts/website_push_www.sh: interpreter directive changed from "#!/bin/bash" to "/nix/store/p6k7xp1lsfmbdd731mlglrdj2d66mr82-bash-5.2p37/bin/bash"
       > stripping (with command strip and flags -S -p) in  /nix/store/3613ljbf4wmc94h9pq4lj0ym6dis4l9y-vagrant-2.4.1/lib /nix/store/3613ljbf4wmc94h9pq4lj0ym6dis4l9y-vagrant-2.4.1/bin
       > rewriting symlink /nix/store/3613ljbf4wmc94h9pq4lj0ym6dis4l9y-vagrant-2.4.1/nix-support/gem-meta/spec to be relative to /nix/store/3613ljbf4wmc94h9pq4lj0ym6dis4l9y-vagrant-2.4.1
       > Running phase: installCheckPhase
       > Vagrant failed to initialize at a very early stage:
       >
       > The plugins failed to initialize correctly. This may be due to manual
       > modifications made within the Vagrant home directory. Vagrant can
       > attempt to automatically correct this issue by running:
       >
       >   vagrant plugin repair
       >
       > If Vagrant was recently updated, this error may be due to incompatible
       > versions of dependencies. To fix this problem please remove and re-install
       > all plugins. Vagrant can attempt to do this automatically by running:
       >
       >   vagrant plugin expunge --reinstall
       >
       > Or you may want to try updating the installed plugins to their latest
       > versions:
       >
       >   vagrant plugin update
       >
       > Error message given during initialization: can't activate rexml-3.3.6, already activated rexml-3.2.5
       For full logs, run 'nix log /nix/store/pzhy4dln2zzixq04cdz4nq3lld1w8ysn-vagrant-2.4.1.drv'.
error: 1 dependencies of derivation '/nix/store/3syns3ic3z48wqhr3ns68cb7a1by98m0-home-manager-path.drv' failed to build
error: 1 dependencies of derivation '/nix/store/hxaysfsn91q7a1g39bn8mvczyjwwv3wa-home-manager-generation.drv' failed to build

自分がビルドしたものが動いているログ

[shouhei@nixos:~/src/github.com/NixOS/nixpkgs]$ git log
commit 3828bc6e1102c6dfe2c8a004fcc115e5b5a467bf (grafted, HEAD -> master, origin/master, origin/HEAD)
Author: Martin Weinelt <hexa@darmstadt.ccc.de>
Date:   Mon Dec 2 14:13:16 2024 +0100

    nixos/kea: fix settings example (#361068)

[shouhei@nixos:~/src/github.com/NixOS/nixpkgs]$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   pkgs/by-name/va/vagrant/gemset.nix
    modified:   pkgs/by-name/va/vagrant/gemset_libvirt.nix
    modified:   pkgs/by-name/va/vagrant/package.nix

no changes added to commit (use "git add" and/or "git commit -a")

[shouhei@nixos:~/src/github.com/NixOS/nixpkgs]$ nix profile install .#vagrant --impure
warning: Git tree '/home/shouhei/src/github.com/NixOS/nixpkgs' is dirty

[shouhei@nixos:~/src/github.com/NixOS/nixpkgs]$ vagrant --version
Vagrant 2.4.3

[shouhei@nixos:~/src/github.com/NixOS/nixpkgs]$ cd ~/workspace/test_vagrant/

[shouhei@nixos:~/workspace/test_vagrant]$ vagrant up --provider=libvirt
Bringing machine 'default' up with 'libvirt' provider...
==> default: Checking if box 'generic/ubuntu2204' version '4.3.12' is up to date...
[fog][WARNING] Unrecognized arguments: libvirt_ip_command
==> default: Creating image (snapshot of base box volume).
==> default: Creating domain with the following settings...
==> default:  -- Name:              test_vagrant_default
==> default:  -- Description:       Source: /home/shouhei/workspace/test_vagrant/Vagrantfile
==> default:  -- Domain type:       kvm
==> default:  -- Cpus:              2
==> default:  -- Feature:           acpi
==> default:  -- Feature:           apic
==> default:  -- Feature:           pae
==> default:  -- Clock offset:      utc
==> default:  -- Memory:            2048M
==> default:  -- Base box:          generic/ubuntu2204
==> default:  -- Storage pool:      default
==> default:  -- Image(vda):        /var/lib/libvirt/images/test_vagrant_default.img, virtio, 128G
==> default:  -- Disk driver opts:  cache='default'
==> default:  -- Graphics Type:     vnc
==> default:  -- Video Type:        cirrus
==> default:  -- Video VRAM:        256
==> default:  -- Video 3D accel:    false
==> default:  -- Keymap:            en-us
==> default:  -- TPM Backend:       passthrough
==> default:  -- INPUT:             type=mouse, bus=ps2
==> default: Creating shared folders metadata...
==> default: Starting domain.
==> default: Domain launching with graphics connection settings...
==> default:  -- Graphics Port:      5900
==> default:  -- Graphics IP:        127.0.0.1
==> default:  -- Graphics Password:  Not defined
==> default:  -- Graphics Websocket: 5700
==> default: Waiting for domain to get an IP address...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 192.168.121.40:22
    default: SSH username: vagrant
    default: SSH auth method: private key
    default:
    default: Vagrant insecure key detected. Vagrant will automatically replace
    default: this with a newly generated keypair for better security.
    default:
    default: Inserting generated public key within guest...
    default: Removing insecure key from the guest if it's present...
    default: Key inserted! Disconnecting and reconnecting using new SSH key...
==> default: Machine booted and ready!

[shouhei@nixos:~/workspace/test_vagrant]$ vagrant ssh
[fog][WARNING] Unrecognized arguments: libvirt_ip_command
vagrant@ubuntu2204:~$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.3 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
vagrant@ubuntu2204:~$ exit
logout