- 投稿日:2019-10-24T23:35:31+09:00
デバッガを作りたいPart1
なぜデバッガ(トレーサー)を作るのか
ここで言うデバッガとは、Linuxマシン上で動く実行ファイルを
デバッガを作ることでどう動いているのかがわかることによって理解が深まることといろいろなカスタマイズができるようにして、楽にデバッグできるようにしたかった。こんかいつくるでデバッガは環境
- Ubuntu 18.04 64bit
- VMware上の仮想マシン
- gcc
1.子プロセスとしてトレーシーを起動させる。
とりあえず子プロセスを作ってみる。
なぜ子プロセスとしてトレーシーを起動させるかというと、楽だからである。どういうことかというと、子プロセスとして起動する際にfork()を使うと親プロセス(トレーサー)には子プロセス(トレーシー)のプロセスIDが帰って来るからだ。プロセスがトレーシーの場合0、エラーのとき0未満が帰ってくる。
次のコードは子プロセスを生成するための最低限のトレーサーのコードだ。tracer1.c#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main () { pid_t child_pid; child_pid = fork(); if(child_pid == 0){ printf("I`m tracee\n"); } else if (child_pid > 0){ printf("I`m tracer and child process id is %d \n", child_pid); } else { printf("error at forking target.\n"); } }で実行すると次のように出た。
user@ubuntu:~/Desktop/tracer$ gcc tracer1.c -o tracer user@ubuntu:~/Desktop/tracer$ ./tracer I`m tracer and child process id is 6448 user@ubuntu:~/Desktop/tracer$ I`m tracee子プロセスのプロセスIDは6448だったようだ。
親プロセスのprintfが先に実行され、子プロセスのprintfはあとに実行された。デバッガを作る際はここによく気をつけなければうまく動いてくれない。ここが難しい。子プロセスとして、別なプログラムを実行してみる。
別なプログラムの例としてここでは、下の簡単なhelloプログラムをトレーサーと同じディレクトリ内に作ってそれを子プロセスとして動かすことを試みる。
hello.c#include <stdio.h> int main () { printf("Hello\n"); return 0; }プロセスの実行には、execl()を使う。execlはパスからプログラムを実行するものです。具体的に見たほうがわかりやすいでしょう。次のコードはrun_child関数を追加しただけです。
tracer2.c##include <stdio.h> #include <sys/types.h> #include <unistd.h> void run_target(){ execl("hello", "hello", NULL); } int main () { pid_t child_pid; child_pid = fork(); if(child_pid == 0){ printf("I`m tracee\n"); run_target(); } else if (child_pid > 0){ printf("I`m tracer and child process id is %d \n", child_pid); } else { printf("error at forking target.\n"); } }で実行すると、
user@ubuntu:~/Desktop/tracer$ ./tracer I`m tracer and child process id is 6601 user@ubuntu:~/Desktop/tracer$ I`m tracee HelloHelloと出たので、ここではうまくいきました。
execlシステムコールはうまくいくと戻ってこないので戻ってきた場合はすべてエラーだったと言うことになります。
子プロセスをトレーシーとして実行することができました!
次からやっとptraceを使ってデバッガを作っていきます。
- 投稿日:2019-10-24T23:35:31+09:00
Linuxのデバッガを自作したい
- 投稿日:2019-10-24T20:16:24+09:00
Linuxコマンドの結果を捨てる時に使う /dev/nulll 2>&1 について簡単にまとめる
目的
- 新人の時にノートに書いた内容を記事にする。
- 案外知らない
/dev/null 2>&1
の意味を簡単に知る。そもそも
/dev/null 2>&1
って?
- コマンドから得られるあらゆる出力を捨てるものである。
何かしらのコマンド > /dev/null 2>&1
とすることにより「何かしらのコマンド」から得られる出力を捨てることができる。- エラーをコンソールに出力したくない時や、敢えてコマンドの結果を隠す時に使用される。
/dev/null 2>&1
の意味は?# | ① |② | ③ | ④ | $ 何かしらのコマンド > /dev/null 2>&1
- 上記のように羅列した内容を区切って簡単に説明する。
① 文字通り何か知らのコマンドが入る。結果を出力したくないコマンドを書く。
② 何かしらのコマンドの結果を後ろの④の2>&1
に渡している。
③ 通常の出力を捨てる。
④ エラーの出力を通常の出力として扱うようにしている。これがあるとエラー出力は全て通常の出力になる。
- 紹介したコマンドは上記のような処理が行われている。
- 一文で書くと「①で結果が出力され、その結果が②によって④に渡され、④でエラー出力も通常の出力も通常出力として扱われ、③で通常の出力を捨てている。」
- 投稿日:2019-10-24T12:48:18+09:00
viがなくてもあわてない
Docker便利ですよね。いざコンテナで作業しようとしたとき、
$ vi bash: vi: command not foundあっ vi がない!ってなりませんか?
そんなときどうしましょう。インストールする?とんでもない!
そんなあなたは今すぐ隣のvimmerと距離を置きましょう。
正解はemacsを使テキストファイルのちょっとした編集を行うテクニックです。
echoとリダイレクト
標準出力を
>
でファイルに上書き、>>
でファイルに追記します。$ echo "文字列" > file.txt $ echo "最終行に追記" >> file.txtcat
catに標準入力
-
を渡すと複数行を書き込み。Ctl+C
で抜けます。$ cat - > file.txt a b c ^Csed
sedは置換コマンドのイメージが強いですが、いろいろできます。そしてviはなくてもsedはたいていあります。
表示
$ sed -n '3p' file.txt # 3行目だけ表示削除
$ sed -i -e '3d' file.txt # 3行目を削除挿入
$ sed -i -e '3i hoge' file.txt # 3行目に挿入置換
$ sed -i -e 's/hoge/fuga/g' file.txt # すべてのhogeをfugaに置換 $ sed -i -e '3 s/hoge/fuga/g' file.txt # 3行目だけ置換
- 投稿日:2019-10-24T06:04:55+09:00
TCPのタイムアウトまでの時間を確認
簡単な確認方法
# 存在しないIP宛にtelnetするとタイムアウトまでの時間がわかる。 $ time telnet 192.168.9.12 Trying 192.168.9.12... telnet: connect to address 192.168.9.12: Connection timed out real 2m7.001s user 0m0.000s sys 0m0.001s# リトライ回数の設定は以下で確認ができます。 $ cat /proc/sys/net/ipv4/tcp_syn_retries 5CentOS 5 の tcp_syn_retries値
値 タイムアウト 備考 5 3m9.132s 初期値 6 5m9.178s CentOS 6.10 の tcp_syn_retries値
値 タイムアウト 備考 5 1m3.043s 初期値 6 2m7.001s 7 4m7.001s 8 6m7.001s (TCP_RTO_MAX値が120なため2分づつ増える)
※キャッシュが有効になっているのか、すぐにタイムアウトすることもあれば、期待値より半分の時間の場合もあった。
tcpdumpを使った確認方法
# eth0のtelnetポートを監視 sudo /usr/sbin/tcpdump -i eth0 port 23# 到達できないIPを指定してtelnet telnet 192.168.9.9
Trying 192.168.9.9...
telnet: Unable to connect to remote host: Connection timed outとなるまで tcpdump の出力を確認。
理論上
# CentOS 6.10 $ cat /proc/sys/net/ipv4/tcp_syn_retries 5 # 結果 0秒(最初のSYN) + 1秒(1回) + 2秒(2回) + 4秒(3回) + 8秒(4回) + 16秒(5回) + 32秒(タイムアウト) = 63秒# CentOS 5 $ cat /proc/sys/net/ipv4/tcp_syn_retries 5 # 結果 0秒(最初のSYN) + 3秒(1回) + 6秒(2回) + 12秒(3回) + 24秒(4回) + 48秒(5回) + 96秒(タイムアウト) = 189秒# Ubuntu 19.10 $ cat /proc/sys/net/ipv4/tcp_syn_retries 6 # 結果 0秒(最初のSYN) + 1秒(1回) + 2秒(2回) + 4秒(3回) + 8秒(4回) + 16秒(5回) + 32秒(6回) + 64秒(タイムアウト) = 127秒TCP_TIMEOUT_INIT値をソースで確認
# CentOSの場合 yum install -y kernel-devel # ubuntuの場合 apt install -y linux-sourceソース確認# CentOS6.9の場合 vim -R /usr/src/kernels/2.6.32-754.23.1.el6.x86_64/include/net/tcp.h # Ubuntu19.10の場合 vim -R /usr/src/linux-headers-5.3.0-19/include/net/tcp.h
- CentOS6.9 も Ubuntu19.10 も同じ1秒だった。
- CentOS 5 では以下のように 3秒
TCP SYN のリトライ回数を変更する方法
$ cat /proc/sys/net/ipv4/tcp_syn_retries 5 $ sudo sh -c "echo 6 > /proc/sys/net/ipv4/tcp_syn_retries" $ cat /proc/sys/net/ipv4/tcp_syn_retries 6
- 恒久的に変える場合
CentOS6以降から /etc/sysctl.d/ があります。
CentOS5では /etc/sysctl.conf に書き込みましょう。/etc/sysctl.d/custom.confに追記net.ipv4.tcp_syn_retries = 6
再起動せずに適用したい場合sysctl -p
参考
tcp_syn_retries (integer; default: 5; Linux 2.2 以降)
アクティブな TCP 接続に初期 SYN の再送を試みる最大回数。 この数値は 255 よりも大きくすべきではない。 デフォルトの値は 5 で、およそ 180 秒に対応する。