Skip to content
Update debugging authored by umaumax's avatar umaumax
[[_TOC_]]
## ワークアラウンド
### 🌟🌟🌟 root権限無しで、`/etc/hosts`ファイルの上書き相当の処理を実行する
[dns - associate IP with hostname without editing /etc/hosts - Unix & Linux Stack Exchange]( https://unix.stackexchange.com/questions/714953/associate-ip-with-hostname-without-editing-etc-hosts )
``` bash
TMP_HOSTS_FILEPATH=./tmp_etc_hosts # 相対パスでもOK
unshare --mount --map-root-user bash -c "mount --bind --make-private "$TMP_HOSTS_FILEPATH" /etc/hosts; ping hoge.example.com"
```
## デバッグについて
### 種類
* ライブデバッグ: プロセスにアタッチしてリアルタイムに行う
* ポストモーテムデバッグ: e.g. coredumpを利用してポストモーテム(死後)に行う
### バグに対処する対策のステップについて考える
* 1. バグをそもそも作らない
* 2. 埋め込んでしまったバグを指摘される前に気がつく
* 3. バグが発生してしまった後
* 3-1. バグの解析をしやすくするか
* 3-2. 再現のしやすさ
## 値のdump
### 数値をdumpする際の注意点
言語のAPIの標準設定よりも、桁数を増やして、より正確な値として出力すること(ただし、より正確に完全一致で比較したい場合にはバイナリ値を見るべき)
### float, doubleのバイナリの値比較例
cpp
``` cpp
#include <iomanip>
#include <iostream>
int main(int argc, const char* argv[]) {
float f = 1.234f;
std::cout << std::hex << std::setw(8) << std::setfill('0') << *reinterpret_cast<uint32_t*>(&f) << std::dec << std::endl;
double d = 1.234;
std::cout << std::hex << std::setw(16) << std::setfill('0') << *reinterpret_cast<uint64_t*>(&d) << std::dec << std::endl;
return 0;
}
```
see: [umaumax/dumpling]( https://github.com/umaumax/dumpling )
rust
``` rust
fn main() {
let x = 1.234_f64;
println!(
"{}",
x.to_be_bytes()
.iter()
.map(|n| format!("{:02x}", n))
.collect::<String>()
);
}
```
### c++: char array to hex std::string
``` cpp
#include <sstream>
std::stringstream ss;
ss << std::hex;
int index=0;
for (const auto &item : test) {
ss << (0xff & static_cast<unsigned int>(item));
if (index % 8 == 7) {
ss << " ";
}
index++;
}
std::cout << ss.str() << std::endl;
```
`ss`は一時変数なので、`ss << std::dec`を省略している
8バイトごとにスペース区切りあり
### Rust: `[u8]` to hex String
see: [umaumax/dumpling]( https://github.com/umaumax/dumpling )
``` rust
str_or_u8_slice
.iter()
.enumerate()
.map(|(i, n)| format!("{:02x}{}", n, if i % 8 == 7 { " " } else { "" }))
.collect::<String>()
```
8バイトごとにスペース区切りあり
### 値をdumpするにはデータ量が多い場合のハッシュ計算例
* c++
* [cpp-examples/md5.cpp at master · umaumax/cpp-examples]( https://github.com/umaumax/cpp-examples/blob/master/hash/md5.cpp )
* [cpp-examples/sha1.cpp at master · umaumax/cpp-examples]( https://github.com/umaumax/cpp-examples/blob/master/hash/sha1.cpp )
required:
``` bash
sudo apt install -y libboost-dev
```
see: [umaumax/dumpling]( https://github.com/umaumax/dumpling )
rust sha1
``` bash
cargo add sha1_smol
```
``` rust
sha1_smol::Sha1::from(&str_or_u8_slice).digest()
```
#### 🔥char arrayをstd::stringに変換する際の注意点
`std::string((const char*)raw_image[i], width*height)`のようにサイズを指定すること(バイナリデータには`'\0'`が含まれている場合があるため)
## システム全体の挙動を解析したい
### ある一定期間の間に実行されたすべてのコマンドを取得したい
``` bash
sudo execsnoop -d 1
```
### ある一定期間の間に開かれたファイルを取得したい
``` bash
sudo opensnoop -d 1
```
## あるコマンドの挙動を解析したい
### 利用する環境変数を知りたい
``` bash
ltrace -e getenv ld -lhoge
```
### アクセスするファイルが知りたい
``` bash
strace -e trace=file ld -lhoge
```
### あるプロセスが現在開いているファイルを知りたい
``` bash
sudo lsof -p $PID
```
* 利用している動的ライブラリの情報も取得できる
* 特に、ログファイルが一発でわかる
### 特定のプロセスの動きを一時的に停止したい
``` bash
kill -SIGSTOP $(pgrep hoge)
# do something
kill -SIGCONT $(pgrep hoge)
```
### あるプロセスが現在実行されているCPUのidを知りたい
`trace-cmd`を利用する
[trace cmd · Wiki · umaumax / memo · GitLab]( https://gitlab.com/umaumax/memo/-/wikis/trace-cmd#cpu%E3%81%AE%E5%88%87%E3%82%8A%E6%9B%BF%E3%82%8F%E3%82%8A%E3%81%AE%E7%9B%A3%E8%A6%96 )
### コンテキストスイッチの切り替え回数をn秒ごとに監視する
下記の`cswch/s nvcswch/s`の項目を見る
``` bash
pidstat -w 1 -p $PID
```
### 特定のパスのコマンドを置き換える方法
* 方法1: `$PATH`を置き換える
* オリジナルのシェル関数(`set_command_logger COMMAND_NAME`)がある
* 方法2: `mount --bind`を利用する
* 直接ファイルパス指定で起動するケースでも置換可能
* ただし、元のファイルが呼べなくなる
### g++コマンドの引数のトレース
`cxx.sh`
``` bash
#!/usr/bin/env bash
set -x
g++ "$@" -g3 -O0
```
オプションを強制的に追加することもできる
## ネットワーク
### TCP通信の中身を気軽に確認したい
詳しく確認するにはtcpdumpコマンドがあるが、そこまでする必要がない場合
curlのリクエストとpythonファイルサーバからのレスポンスを確認することができる
``` bash
# temirnal A
python3 -m http.server 8080
# terminal B
socat -v TCP4-LISTEN:18080,fork TCP4:localhost:8080
# terminal C
curl localhost:18080
```
``` bash
$ socat -v TCP4-LISTEN:18080,fork TCP4:localhost:8080
> 2023/11/20 13:16:34.640853 length=78 from=0 to=77
GET / HTTP/1.1\r
Host: localhost:18080\r
User-Agent: curl/8.1.2\r
Accept: */*\r
\r
< 2023/11/20 13:16:34.643186 length=155 from=0 to=154
HTTP/1.0 200 OK\r
Server: SimpleHTTP/0.6 Python/3.9.12\r
Date: Mon, 20 Nov 2023 04:16:34 GMT\r
Content-type: text/html; charset=utf-8\r
Content-Length: 297\r
\r
< 2023/11/20 13:16:34.643715 length=297 from=155 to=451
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
</ul>
<hr>
</body>
</html>
```
### サーバアクセスのデバッグ
#### サーバからの応答がなく、永久待機状態となった
1. サーバの起動
2. (この時点で、ポートへのアクセスを受け付け始める)
3. 特定のパス(e.g. `/health`)の待受が完了する
* __上記の2番の状態でクライアントが接続している場合に、ソケットのタイムアウト設定がない場合には永久に待機状態となる場合があることに注意__
* タイミング問題であるので、サーバが完全に起動した後でデバッグをしても、気が付かないことに注意
* 仮説としては、3番まで起動しているが、`keep-alive`の影響で永久待機状態となっていると思いながらデバッグしていた
## 目的別
### 特定の共有ファイルを利用しているプロセス一覧
``` bash
lsof /lib/x86_64-linux-gnu/libnss_files-2.23.so
ps $(grep /lib/x86_64-linux-gnu/libnss_files-2.23.so /proc/*/maps | cut -d / -f3 | sort -u)
```
### 遅延読み込みのライブラリが今現在、読み込まれているかどうかを知りたい
`pmap`コマンドや`/proc/$PID/maps`を利用すれば、どのライブラリがどこのメモリマップにあるかわかる
### スタック破壊を検出したい
* stack protectorはリリース向けでも適用可能である(オーバーヘッドがほぼないため、リリース向けで実績あり)
* 破壊された場合には`assert`となるが、そもそも、スタック破壊された場合には予期しない挙動となるので、`assert`で処理が終了するほうがまだよい挙動である
* [Instrumentation Options (Using the GNU Compiler Collection (GCC))]( https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html )
* `CFLAGS += -D_FORTIFY_SOURCE=1 -fstack-protector-strong`
* [全てのプログラムに gcc の -D_FORTIFY_SOURCE と -fstack-protector オプションが効くわけではない話 | かわうそ通信]( https://konpeitoh.net/tech/stack-protector_20220724/ )
## 各スレッドがどのようなスレッドかどうかを知りたい
### あるプロセスの各スレッドで実行した動的リンクした関数の一覧を取得して,図示したい
e.g. `xargs echo <<(ls)`
``` bash
function dot_wrapper() {
awk '
BEGIN { printf "digraph G {\n" }
{
printf "\"%s\" -> \"%s\"\n", $1, $2;
}
END { printf "}\n" }
'
}
ltrace -f xargs echo <<(ls) |& sort | sed -E 's/\[|\]|[()]/ /g' | grep pid | awk '{printf "%d %s\n",$2,$3; }' | grep -v '+++\|---\|<...' | sort | uniq > func.log
cat func.log | dot_wrapper | dot -Tsvg -o output.svg
```
### LD_PRELOADでPIDとスレッド生成時のバックトレースを表示する
[umaumax/pthreadinfo]( https://github.com/umaumax/pthreadinfo )
`pthread_create`を呼び出したときのバックトレースを出力するツール
### 後からすでに動作中のスレッドのバックトレースを取得する
``` bash
sudo gstack $PID
```
内部的には、`gdb`を利用しているとのこと
### すべてのスレッドのbtをdumpする例
``` bash
cat << EOF | gdb -q -p $PID
set pagination off
set logging file ./gdb.log
set logging on
thread apply all bt
EOF
```
この結果のdiffをとれば、概ね停止しているスレッドのbtは一致するのでどのスレッドが実際に動作しているのかのイメージが掴める
or
``` bash
PID=XXX ; while true; do sleep 0.01; { date +%s%3N; cat /proc/$PID/task/*/stat | awk -v SC_CLK_TCK=100 '{ tid=$1; state=$3; print tid, state; }'; } | tee -a ps.log; done | grep -v S
```
として定期的に情報をdumpすると、動いているスレッドがわかる
``` bash
git diff --word-diff --no-index -- gdb1.log gdb2.log
```
## ビルド
### バージョン違いによる不具合の具体例
ターゲットAでビルドしたものをターゲットBで利用するとき(クロスビルドでなくてもよい),例えば,dockerでビルドしたrosパッケージをホストマシンで実行するときに、`apt-get`で入れているライブラリ(`ros`)のバージョンがinstallした時期によって異なるので,`sudo apt list --upgradable`として確認するとよい
### ソースからビルドする際の注意点
githubでtagを利用してcheckoutする場合とリリースファイルを利用する場合で内容が変化することがあり、基本的にはリリース物を利用する手順が正しい
(意外とこのケースに遭遇し、リリースファイルを利用するとビルドエラーなどの問題が解決することが多い)
e.g. [Build fail on Ubuntu 18\.04 x86\_64 · Issue \#35 · twistedfall/opencv\-rust]( https://github.com/twistedfall/opencv-rust/issues/35 )
### 異なる環境でビルドした成果物が同等であるかどうかの妥当性の検証をしたい
``` sh
find . -name "*.so" | xargs -n1 md5sum
find . -name "*.so" | xargs -n1 -I{} bash -c "echo {}; strings {} | md5sum"
```
* 実際に、異なる環境でビルドした結果でも上記のように`strings`を通すと一致する場合があったが、これは、あくまでも、デバッグの目安にしかならず、確実性はないことに注意
* `strings`を行う前に`strip`をかませるとよいかもしれない(`strip`は指定したファイル自体に変更を加えることに注意)
バイナリファイルかどうかは`-executable`の他に拡張子が`.sh`であるファイルも対象になることに注意
### buildできない or 意図しない実行結果となる
* `git checkout`をした後にそのままbuildした場合
* `git rebase`をした後にそのままbuildした場合
上記,依存関係の設定が不完全な`make`でよく起こり得る
### バイナリコマンドの差分検出
#### どうしても、データが異なる場合の説明?
バイナリデータのアライメントを揃える目的のダミーデータの可能性は?
#### バイナリ差分の比較手順の考察
* `strip -s`コマンドでヘッダ情報を削除する
* `objdump -x`or`objdump -d`でdiffをとる
#### 既存ツール
[elfdiff \- マニュアルページ セクション 1: ユーザーコマンド]( https://docs.oracle.com/cd/F16635_01/html/E71065/elfdiff-1.html )
Oracle Solarisのmanualがあるが実装は簡単に見れる範囲ではweb上では見つからない(OSをダウンロードすれば見れそうだが)
[elfdiff \- man pages section 1: User Commands]( https://docs.oracle.com/cd/E88353_01/html/E37839/elfdiff-1.html )
特に比較したいセクション
> Sections that contain program data, whose interpretation is meaningful only to the application. These sections include the program .text instructions and the associated data sections such as .rodata and .data.
> Sections that contain link-editing information, such as the symbol table information found in the .symtab and .strtab sections, and relocation information such as the .rela.text section.
[mvertescher/elfdiff: Simple ELF difftool]( https://github.com/mvertescher/elfdiff )
rust実装で、やっていることは同じ
[dnezamaev/pyelfcmp: Deep comparing ELF files in Python]( https://github.com/dnezamaev/pyelfcmp )
python実装で、やっていることは同じ
[CapeLeidokos/elf\_diff: A tool to compare ELF binaries]( https://github.com/CapeLeidokos/elf_diff )
python実装で、比較項目がバイナリデータのdiffではなく、各セクション数などであるので出力形式が異なる
[abidiff\(1\) \- Linux manual page]( https://man7.org/linux/man-pages/man1/abidiff.1.html )
対象ファイルが共有ライブラリのみのdiffかつ比較項目がバイナリデータのdiffではなく、関数や変数の数などであるので出力形式が異なる
abidiff
``` bash
sudo apt-get install abigail-tools
```
## トラブルシューティング
### 🔥html/css/javascriptファイルの変更が反映されない
ウェブブラウザの更新時に通常ロードではなく、サーバからデータを強制的に取得する`ハードロード`を実施すること(Macでは`Command+Shift+R`)
### ログインできない
パスワード入力時に特に、ユーザ名の先頭にスペースが入り込んでいると見た目で判断しにくいことに注意
(特に、ディスプレイを反応させるために、スペースキーを打った時など)
### 外部と通信する処理での意図しないエラー
クライアントマシンの時刻があっているかどうかを確認すること
### ビルドで差分が適用されない
NTPサーバは正しく設定すること
少なくとも、時間がずれていてもよいが,時間がジャンプしていないことを確認すること
そうしないと,`make`で正しく差分ビルドされなくなることに注意
### ウェブからのコピペしたコマンドの動作がおかしい(スペースが怪しい場合)
[C2A0問題を解決する]( https://lab.unicast.ne.jp/2013/10/22/fix-c2a0-problem/ )
### セットアップツールなどのGUIとCUI両対応コマンドをCUI想定で起動した際にハングアップしている用に見える問題
これはGUIで待機状態となっているために、おかしな状況に見えるので、特にリモート作業中はこれに注意すること
しかし、この場合、GUIで勧めていくとハングアップする経験が多いので、基本的にはCUIをおすすめする
### `sudo passwd`だとroot userの設定をすることになるので、注意
``` bash
sudo passwd $USER
```
### プロセスがたまに停止しているようにみえる場合
HWの割り込み処理などはCPU0に集中するような設定になっていることが多いが関連があるかもしれない
`cat /proc/interrupts`で確認できる
上記の割り込みをよく利用するプロセスが時々停止してしまうことがある場合にはtasksetを利用してCPU0に余計なプロセスを割り当てないアフィニティとするか、割り込みを他のCPUでも許可する?(未検証)
### pkillの挙動が奇妙な場合
* 他のユーザや自分が`tmux`経由でログインしているときに、プロセスが動いているままの可能性はないか?
* 特に、あるプログラムを動かして、一定時間後に`pkill`する場合に、他のユーザの権限で動作しているプログラムの方が若いPIDだと、`pkill`の処理がそこで止まる疑惑がある => いや,signal自体はすべての該当プロセスに飛ぶので,これは関係ない
### 特定の実行ファイルを決まった場所に配置する運用で、`Text file busy`を回避したい
予め、シンボリックリンクで実行ファイルを参照する形式にしておいて、その参照先を差し替える形式とすればよい
### `Makefile`が`makefile`となっているケースに注意
問題なくmakeコマンドで利用できるが、find時には注意
### あるプロセスでbad_allocが発生した場合のデバッグ方法
* 長時間稼働するプロセスならば、外部から観測する
* 短い時間に、一気にメモリが利用される場合は外部からの観測では傾向がわからないので、内部から観測できるようにする
例えば、`std::bad_alloc`の例外を補足して、プロセスを停止すれば、その時点の`/proc/buddyinfo`などを観測できる
具体的な方法として、`LD_PRELOAD``new`を置き換えて、その中で、try catchして、`raise(SIGSTOP)`して停止している状態で`cat /proc/buddyinfo`を見る
特にメモリが不足しているようには見えない場合は、不正なサイズで巨大なメモリ確保が起きている可能性が考えられる(特に、未初期化領域の数値を参照したサイズを確保しているケースが考えられる)
### SEGVしなさそうな場所でSEGVした場合のデバッグ
伝播(でんぱ)した結果SEGVした可能性があるので、N個前に未初期化領域にアクセスしていないかどうかなどを見ること
例えば、未初期化領域のメソッドを呼んだ場合など
``` cpp
class Hoge {
public:
void NotifyOne() { condition_variable_.notify_one(); }
private:
std::condition_variable condition_variable_;
};
int main() {
std::shared_ptr<Hoge> hoge;
hoge->NotifyOne();
return 0;
}
```
```
#0 pthread_cond_signal@@GLIBC_2.3.2 () at ../sysdeps/unix/sysv/linux/x86_64/pthread_cond_signal.S:39
#1 0x00007ffff7b08939 in std::condition_variable::notify_one() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#2 0x00000000004009a8 in Hoge::NotifyOne() ()
#3 0x000000000040090f in main ()
```
### 意図しないSEGV
想定と異なるバージョンのheaderや共有ライブラリを組み合わせて利用してしまっている可能性がある
特に、システムとローカルの両方にライブラリがある場合になりやすい`gtest`などで発生しがち
### 意図しない挙動
* `printf`でのフォーマット間違いは意図しない挙動を引き起こす可能性が非常に高いことに注意
* 予想不能な不可解な場合は個のケースが多い
* 特に、intの型やStringに対して`c_str()`を使わずに、`%s`とするパターンが多い
### 例外が発生した箇所(try-catchしている場合を含む)のスタックトレースを取得したい
[tnakagome/exray: Show stack traces from C\+\+ exceptions at run\-time without rebuilding target applications]( https://github.com/tnakagome/exray )
`LD_PRELOAD`を利用することで、リビルドが不要なツール(まだ、試したことはない)
### CPUの負荷が高いときに注意するプロセス
* neovim経由で実行するプラグイン系のプロセス
* `semgrep-core`
* LSPデーモン
* tmuxプロセスの暴走
* Macの`pboard`プロセスが暴走するとGUIが固まる
* ほかマシンからのsshは可能
## 負荷をかけたい
### CPU
``` bash
yes > /dev/null
```
stressコマンド
[simple stress command]( https://gist.github.com/umaumax/7c9018a60a001bfc919afda7003bf032 )
### メモリ使用率
``` bash
/dev/null < $(yes)
```
実行しているbashやzshなどのプロセスの`VIRT``RES`が上がり、`zsh: fatal error: out of heap memory`となって、プロセスが落ちる
## 単体テスト
### たまに失敗する場合の原因
* 時間が関わっているのでは?
* 短時間の連続リクエストの拒否ロジックなど
* 非同期APIの結果待ちなど
* スレッド起動後の処理タイミングが想定と異なる(テスト特有の立ち上げてからすぐに終了するようなケース)
* e.g. condition variableのwaitが行われる前にnotifyが行われ、ずっとwaitし続けるなど
## 再現
### gdb実行時にSEGVなどが再現しなくなる場合は、普通に実行し、coredumpする設定とすれば良い
## 読み物
### [Linuxのloadavgが約7時間ごとに上昇する現象の原因 \- Mackerel お知らせ \#mackerelio]( https://mackerel.io/ja/blog/entry/tech/high-loadavg-every-7-hours )
\ No newline at end of file
[[_TOC_]]
## ワークアラウンド
### :star2::star2::star2: root権限無しで、`/etc/hosts`ファイルの上書き相当の処理を実行する
[dns - associate IP with hostname without editing /etc/hosts - Unix & Linux Stack Exchange](https://unix.stackexchange.com/questions/714953/associate-ip-with-hostname-without-editing-etc-hosts)
```bash
TMP_HOSTS_FILEPATH=./tmp_etc_hosts # 相対パスでもOK
unshare --mount --map-root-user bash -c "mount --bind --make-private "$TMP_HOSTS_FILEPATH" /etc/hosts; ping hoge.example.com"
```
## デバッグについて
### 種類
* ライブデバッグ: プロセスにアタッチしてリアルタイムに行う
* ポストモーテムデバッグ: e.g. coredumpを利用してポストモーテム(死後)に行う
### バグに対処する対策のステップについて考える
* バグをそもそも作らない
* 埋め込んでしまったバグを指摘される前に気がつく
* バグが発生してしまった後
* 3-1. バグの解析をしやすくするか
* 3-2. 再現のしやすさ
## 値のdump
### 数値をdumpする際の注意点
言語のAPIの標準設定よりも、桁数を増やして、より正確な値として出力すること(ただし、より正確に完全一致で比較したい場合にはバイナリ値を見るべき)
### float, doubleのバイナリの値比較例
cpp
```cpp
#include <iomanip>
#include <iostream>
int main(int argc, const char* argv[]) {
float f = 1.234f;
std::cout << std::hex << std::setw(8) << std::setfill('0') << *reinterpret_cast<uint32_t*>(&f) << std::dec << std::endl;
double d = 1.234;
std::cout << std::hex << std::setw(16) << std::setfill('0') << *reinterpret_cast<uint64_t*>(&d) << std::dec << std::endl;
return 0;
}
```
see: [umaumax/dumpling](https://github.com/umaumax/dumpling) rust
```rust
fn main() {
let x = 1.234_f64;
println!(
"{}",
x.to_be_bytes()
.iter()
.map(|n| format!("{:02x}", n))
.collect::<String>()
);
}
```
### c++: char array to hex std::string
```cpp
#include <sstream>
std::stringstream ss;
ss << std::hex;
int index=0;
for (const auto &item : test) {
ss << (0xff & static_cast<unsigned int>(item));
if (index % 8 == 7) {
ss << " ";
}
index++;
}
std::cout << ss.str() << std::endl;
```
`ss`は一時変数なので、`ss << std::dec`を省略している
8バイトごとにスペース区切りあり
### Rust: `[u8]` to hex String
see: [umaumax/dumpling](https://github.com/umaumax/dumpling)
```rust
str_or_u8_slice
.iter()
.enumerate()
.map(|(i, n)| format!("{:02x}{}", n, if i % 8 == 7 { " " } else { "" }))
.collect::<String>()
```
8バイトごとにスペース区切りあり
### 値をdumpするにはデータ量が多い場合のハッシュ計算例
* c++
* [cpp-examples/md5.cpp at master · umaumax/cpp-examples](https://github.com/umaumax/cpp-examples/blob/master/hash/md5.cpp)
* [cpp-examples/sha1.cpp at master · umaumax/cpp-examples](https://github.com/umaumax/cpp-examples/blob/master/hash/sha1.cpp)
required:
```bash
sudo apt install -y libboost-dev
```
see: [umaumax/dumpling](https://github.com/umaumax/dumpling) rust sha1
```bash
cargo add sha1_smol
```
```rust
sha1_smol::Sha1::from(&str_or_u8_slice).digest()
```
#### :fire:char arrayをstd::stringに変換する際の注意点
`std::string((const char*)raw_image[i], width*height)`のようにサイズを指定すること(バイナリデータには`'\0'`が含まれている場合があるため)
## 他組織の人と統合動作を検証する場合
* 💡**何かしらの変更が加えられていないかどうか**
* 必ず確認すること
* 特にバージョンや設定の変更が原因となることが多いので注意すること
* どこまで問題ないかどうかを実際に手を動かして細かく切り分けすること(ある程度当たりはつけても良いが、枝刈りをしすぎると、見落としに全然気が付かなくなることに注意)
* 具体的なエラーの内容に囚われすぎずに、バージョンの不一致や環境差分、設定など広い範囲についても考えるとよい
## システム全体の挙動を解析したい
### ある一定期間の間に実行されたすべてのコマンドを取得したい
```bash
sudo execsnoop -d 1
```
### ある一定期間の間に開かれたファイルを取得したい
```bash
sudo opensnoop -d 1
```
## あるコマンドの挙動を解析したい
### 利用する環境変数を知りたい
```bash
ltrace -e getenv ld -lhoge
```
### アクセスするファイルが知りたい
```bash
strace -e trace=file ld -lhoge
```
### あるプロセスが現在開いているファイルを知りたい
```bash
sudo lsof -p $PID
```
* 利用している動的ライブラリの情報も取得できる
* 特に、ログファイルが一発でわかる
### 特定のプロセスの動きを一時的に停止したい
```bash
kill -SIGSTOP $(pgrep hoge)
# do something
kill -SIGCONT $(pgrep hoge)
```
### あるプロセスが現在実行されているCPUのidを知りたい
`trace-cmd`を利用する
[trace cmd · Wiki · umaumax / memo · GitLab](https://gitlab.com/umaumax/memo/-/wikis/trace-cmd#cpu%E3%81%AE%E5%88%87%E3%82%8A%E6%9B%BF%E3%82%8F%E3%82%8A%E3%81%AE%E7%9B%A3%E8%A6%96)
### コンテキストスイッチの切り替え回数をn秒ごとに監視する
下記の`cswch/s nvcswch/s`の項目を見る
```bash
pidstat -w 1 -p $PID
```
### 特定のパスのコマンドを置き換える方法
* 方法1: `$PATH`を置き換える
* オリジナルのシェル関数(`set_command_logger COMMAND_NAME`)がある
* 方法2: `mount --bind`を利用する
* 直接ファイルパス指定で起動するケースでも置換可能
* ただし、元のファイルが呼べなくなる
### g++コマンドの引数のトレース
`cxx.sh`
```bash
#!/usr/bin/env bash
set -x
g++ "$@" -g3 -O0
```
オプションを強制的に追加することもできる
## ネットワーク
### TCP通信の中身を気軽に確認したい
詳しく確認するにはtcpdumpコマンドがあるが、そこまでする必要がない場合
curlのリクエストとpythonファイルサーバからのレスポンスを確認することができる
```bash
# temirnal A
python3 -m http.server 8080
# terminal B
socat -v TCP4-LISTEN:18080,fork TCP4:localhost:8080
# terminal C
curl localhost:18080
```
```bash
$ socat -v TCP4-LISTEN:18080,fork TCP4:localhost:8080
> 2023/11/20 13:16:34.640853 length=78 from=0 to=77
GET / HTTP/1.1\r
Host: localhost:18080\r
User-Agent: curl/8.1.2\r
Accept: */*\r
\r
< 2023/11/20 13:16:34.643186 length=155 from=0 to=154
HTTP/1.0 200 OK\r
Server: SimpleHTTP/0.6 Python/3.9.12\r
Date: Mon, 20 Nov 2023 04:16:34 GMT\r
Content-type: text/html; charset=utf-8\r
Content-Length: 297\r
\r
< 2023/11/20 13:16:34.643715 length=297 from=155 to=451
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
</ul>
<hr>
</body>
</html>
```
### サーバアクセスのデバッグ
#### サーバからの応答がなく、永久待機状態となった
1. サーバの起動
2. (この時点で、ポートへのアクセスを受け付け始める)
3. 特定のパス(e.g. `/health`)の待受が完了する
* **上記の2番の状態でクライアントが接続している場合に、ソケットのタイムアウト設定がない場合には永久に待機状態となる場合があることに注意**
* タイミング問題であるので、サーバが完全に起動した後でデバッグをしても、気が付かないことに注意
* 仮説としては、3番まで起動しているが、`keep-alive`の影響で永久待機状態となっていると思いながらデバッグしていた
## 目的別
### 特定の共有ファイルを利用しているプロセス一覧
```bash
lsof /lib/x86_64-linux-gnu/libnss_files-2.23.so
ps $(grep /lib/x86_64-linux-gnu/libnss_files-2.23.so /proc/*/maps | cut -d / -f3 | sort -u)
```
### 遅延読み込みのライブラリが今現在、読み込まれているかどうかを知りたい
`pmap`コマンドや`/proc/$PID/maps`を利用すれば、どのライブラリがどこのメモリマップにあるかわかる
### スタック破壊を検出したい
* stack protectorはリリース向けでも適用可能である(オーバーヘッドがほぼないため、リリース向けで実績あり)
* 破壊された場合には`assert`となるが、そもそも、スタック破壊された場合には予期しない挙動となるので、`assert`で処理が終了するほうがまだよい挙動である
* [Instrumentation Options (Using the GNU Compiler Collection (GCC))](https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html)
* `CFLAGS += -D_FORTIFY_SOURCE=1 -fstack-protector-strong`
* [全てのプログラムに gcc の -D_FORTIFY_SOURCE と -fstack-protector オプションが効くわけではない話 | かわうそ通信](https://konpeitoh.net/tech/stack-protector_20220724/)
## 各スレッドがどのようなスレッドかどうかを知りたい
### あるプロセスの各スレッドで実行した動的リンクした関数の一覧を取得して,図示したい
e.g. `xargs echo <<(ls)`
```bash
function dot_wrapper() {
awk '
BEGIN { printf "digraph G {\n" }
{
printf "\"%s\" -> \"%s\"\n", $1, $2;
}
END { printf "}\n" }
'
}
ltrace -f xargs echo <<(ls) |& sort | sed -E 's/\[|\]|[()]/ /g' | grep pid | awk '{printf "%d %s\n",$2,$3; }' | grep -v '+++\|---\|<...' | sort | uniq > func.log
cat func.log | dot_wrapper | dot -Tsvg -o output.svg
```
### LD_PRELOADでPIDとスレッド生成時のバックトレースを表示する
[umaumax/pthreadinfo](https://github.com/umaumax/pthreadinfo)
`pthread_create`を呼び出したときのバックトレースを出力するツール
### 後からすでに動作中のスレッドのバックトレースを取得する
```bash
sudo gstack $PID
```
内部的には、`gdb`を利用しているとのこと
### すべてのスレッドのbtをdumpする例
```bash
cat << EOF | gdb -q -p $PID
set pagination off
set logging file ./gdb.log
set logging on
thread apply all bt
EOF
```
この結果のdiffをとれば、概ね停止しているスレッドのbtは一致するのでどのスレッドが実際に動作しているのかのイメージが掴める
or
```bash
PID=XXX ; while true; do sleep 0.01; { date +%s%3N; cat /proc/$PID/task/*/stat | awk -v SC_CLK_TCK=100 '{ tid=$1; state=$3; print tid, state; }'; } | tee -a ps.log; done | grep -v S
```
として定期的に情報をdumpすると、動いているスレッドがわかる
```bash
git diff --word-diff --no-index -- gdb1.log gdb2.log
```
## ビルド
### バージョン違いによる不具合の具体例
ターゲットAでビルドしたものをターゲットBで利用するとき(クロスビルドでなくてもよい),例えば,dockerでビルドしたrosパッケージをホストマシンで実行するときに、`apt-get`で入れているライブラリ(`ros`)のバージョンがinstallした時期によって異なるので,`sudo apt list --upgradable`として確認するとよい
### ソースからビルドする際の注意点
githubでtagを利用してcheckoutする場合とリリースファイルを利用する場合で内容が変化することがあり、基本的にはリリース物を利用する手順が正しい (意外とこのケースに遭遇し、リリースファイルを利用するとビルドエラーなどの問題が解決することが多い)
e.g. [Build fail on Ubuntu 18.04 x86_64 · Issue #35 · twistedfall/opencv-rust](https://github.com/twistedfall/opencv-rust/issues/35)
### 異なる環境でビルドした成果物が同等であるかどうかの妥当性の検証をしたい
```sh
find . -name "*.so" | xargs -n1 md5sum
find . -name "*.so" | xargs -n1 -I{} bash -c "echo {}; strings {} | md5sum"
```
* 実際に、異なる環境でビルドした結果でも上記のように`strings`を通すと一致する場合があったが、これは、あくまでも、デバッグの目安にしかならず、確実性はないことに注意
* `strings`を行う前に`strip`をかませるとよいかもしれない(`strip`は指定したファイル自体に変更を加えることに注意)
バイナリファイルかどうかは`-executable`の他に拡張子が`.sh`であるファイルも対象になることに注意
### buildできない or 意図しない実行結果となる
* `git checkout`をした後にそのままbuildした場合
* `git rebase`をした後にそのままbuildした場合
上記,依存関係の設定が不完全な`make`でよく起こり得る
### バイナリコマンドの差分検出
#### どうしても、データが異なる場合の説明?
バイナリデータのアライメントを揃える目的のダミーデータの可能性は?
#### バイナリ差分の比較手順の考察
* `strip -s`コマンドでヘッダ情報を削除する
* `objdump -x`or`objdump -d`でdiffをとる
#### 既存ツール
[elfdiff - マニュアルページ セクション 1: ユーザーコマンド](https://docs.oracle.com/cd/F16635_01/html/E71065/elfdiff-1.html)
Oracle Solarisのmanualがあるが実装は簡単に見れる範囲ではweb上では見つからない(OSをダウンロードすれば見れそうだが)
[elfdiff - man pages section 1: User Commands](https://docs.oracle.com/cd/E88353_01/html/E37839/elfdiff-1.html)
特に比較したいセクション
> Sections that contain program data, whose interpretation is meaningful only to the application. These sections include the program .text instructions and the associated data sections such as .rodata and .data.
> Sections that contain link-editing information, such as the symbol table information found in the .symtab and .strtab sections, and relocation information such as the .rela.text section.
[mvertescher/elfdiff: Simple ELF difftool](https://github.com/mvertescher/elfdiff) rust実装で、やっていることは同じ
[dnezamaev/pyelfcmp: Deep comparing ELF files in Python](https://github.com/dnezamaev/pyelfcmp) python実装で、やっていることは同じ
[CapeLeidokos/elf_diff: A tool to compare ELF binaries](https://github.com/CapeLeidokos/elf_diff) python実装で、比較項目がバイナリデータのdiffではなく、各セクション数などであるので出力形式が異なる
[abidiff(1) - Linux manual page](https://man7.org/linux/man-pages/man1/abidiff.1.html) 対象ファイルが共有ライブラリのみのdiffかつ比較項目がバイナリデータのdiffではなく、関数や変数の数などであるので出力形式が異なる
abidiff
```bash
sudo apt-get install abigail-tools
```
## トラブルシューティング
### :fire:html/css/javascriptファイルの変更が反映されない
ウェブブラウザの更新時に通常ロードではなく、サーバからデータを強制的に取得する`ハードロード`を実施すること(Macでは`Command+Shift+R`)
### ログインできない
パスワード入力時に特に、ユーザ名の先頭にスペースが入り込んでいると見た目で判断しにくいことに注意 (特に、ディスプレイを反応させるために、スペースキーを打った時など)
### 外部と通信する処理での意図しないエラー
クライアントマシンの時刻があっているかどうかを確認すること
### ビルドで差分が適用されない
NTPサーバは正しく設定すること
少なくとも、時間がずれていてもよいが,時間がジャンプしていないことを確認すること
そうしないと,`make`で正しく差分ビルドされなくなることに注意
### ウェブからのコピペしたコマンドの動作がおかしい(スペースが怪しい場合)
[C2A0問題を解決する](https://lab.unicast.ne.jp/2013/10/22/fix-c2a0-problem/)
### セットアップツールなどのGUIとCUI両対応コマンドをCUI想定で起動した際にハングアップしている用に見える問題
これはGUIで待機状態となっているために、おかしな状況に見えるので、特にリモート作業中はこれに注意すること
しかし、この場合、GUIで勧めていくとハングアップする経験が多いので、基本的にはCUIをおすすめする
### `sudo passwd`だとroot userの設定をすることになるので、注意
```bash
sudo passwd $USER
```
### プロセスがたまに停止しているようにみえる場合
HWの割り込み処理などはCPU0に集中するような設定になっていることが多いが関連があるかもしれない `cat /proc/interrupts`で確認できる
上記の割り込みをよく利用するプロセスが時々停止してしまうことがある場合にはtasksetを利用してCPU0に余計なプロセスを割り当てないアフィニティとするか、割り込みを他のCPUでも許可する?(未検証)
### pkillの挙動が奇妙な場合
* 他のユーザや自分が`tmux`経由でログインしているときに、プロセスが動いているままの可能性はないか?
* 特に、あるプログラムを動かして、一定時間後に`pkill`する場合に、他のユーザの権限で動作しているプログラムの方が若いPIDだと、`pkill`の処理がそこで止まる疑惑がある =\> いや,signal自体はすべての該当プロセスに飛ぶので,これは関係ない
### 特定の実行ファイルを決まった場所に配置する運用で、`Text file busy`を回避したい
予め、シンボリックリンクで実行ファイルを参照する形式にしておいて、その参照先を差し替える形式とすればよい
### `Makefile`が`makefile`となっているケースに注意
問題なくmakeコマンドで利用できるが、find時には注意
### あるプロセスでbad_allocが発生した場合のデバッグ方法
* 長時間稼働するプロセスならば、外部から観測する
* 短い時間に、一気にメモリが利用される場合は外部からの観測では傾向がわからないので、内部から観測できるようにする
例えば、`std::bad_alloc`の例外を補足して、プロセスを停止すれば、その時点の`/proc/buddyinfo`などを観測できる
具体的な方法として、`LD_PRELOAD``new`を置き換えて、その中で、try catchして、`raise(SIGSTOP)`して停止している状態で`cat /proc/buddyinfo`を見る
特にメモリが不足しているようには見えない場合は、不正なサイズで巨大なメモリ確保が起きている可能性が考えられる(特に、未初期化領域の数値を参照したサイズを確保しているケースが考えられる)
### SEGVしなさそうな場所でSEGVした場合のデバッグ
伝播(でんぱ)した結果SEGVした可能性があるので、N個前に未初期化領域にアクセスしていないかどうかなどを見ること
例えば、未初期化領域のメソッドを呼んだ場合など
```cpp
class Hoge {
public:
void NotifyOne() { condition_variable_.notify_one(); }
private:
std::condition_variable condition_variable_;
};
int main() {
std::shared_ptr<Hoge> hoge;
hoge->NotifyOne();
return 0;
}
```
```
#0 pthread_cond_signal@@GLIBC_2.3.2 () at ../sysdeps/unix/sysv/linux/x86_64/pthread_cond_signal.S:39
#1 0x00007ffff7b08939 in std::condition_variable::notify_one() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#2 0x00000000004009a8 in Hoge::NotifyOne() ()
#3 0x000000000040090f in main ()
```
### 意図しないSEGV
想定と異なるバージョンのheaderや共有ライブラリを組み合わせて利用してしまっている可能性がある
特に、システムとローカルの両方にライブラリがある場合になりやすい`gtest`などで発生しがち
### 意図しない挙動
* `printf`でのフォーマット間違いは意図しない挙動を引き起こす可能性が非常に高いことに注意
* 予想不能な不可解な場合は個のケースが多い
* 特に、intの型やStringに対して`c_str()`を使わずに、`%s`とするパターンが多い
### 例外が発生した箇所(try-catchしている場合を含む)のスタックトレースを取得したい
[tnakagome/exray: Show stack traces from C++ exceptions at run-time without rebuilding target applications](https://github.com/tnakagome/exray)
`LD_PRELOAD`を利用することで、リビルドが不要なツール(まだ、試したことはない)
### CPUの負荷が高いときに注意するプロセス
* neovim経由で実行するプラグイン系のプロセス
* `semgrep-core`
* LSPデーモン
* tmuxプロセスの暴走
* Macの`pboard`プロセスが暴走するとGUIが固まる
* ほかマシンからのsshは可能
## 負荷をかけたい
### CPU
```bash
yes > /dev/null
```
stressコマンド
[simple stress command](https://gist.github.com/umaumax/7c9018a60a001bfc919afda7003bf032)
### メモリ使用率
```bash
/dev/null < $(yes)
```
実行しているbashやzshなどのプロセスの`VIRT``RES`が上がり、`zsh: fatal error: out of heap memory`となって、プロセスが落ちる
## 単体テスト
### たまに失敗する場合の原因
* 時間が関わっているのでは?
* 短時間の連続リクエストの拒否ロジックなど
* 非同期APIの結果待ちなど
* スレッド起動後の処理タイミングが想定と異なる(テスト特有の立ち上げてからすぐに終了するようなケース) \* e.g. condition variableのwaitが行われる前にnotifyが行われ、ずっとwaitし続けるなど
## 再現
### gdb実行時にSEGVなどが再現しなくなる場合は、普通に実行し、coredumpする設定とすれば良い
## 読み物
### [Linuxのloadavgが約7時間ごとに上昇する現象の原因 - Mackerel お知らせ #mackerelio](https://mackerel.io/ja/blog/entry/tech/high-loadavg-every-7-hours)
\ No newline at end of file