20191024のLinuxに関する記事は5件です。

デバッガを作りたい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
Hello

Helloと出たので、ここではうまくいきました。
execlシステムコールはうまくいくと戻ってこないので戻ってきた場合はすべてエラーだったと言うことになります。
子プロセスをトレーシーとして実行することができました!
次からやっとptraceを使ってデバッガを作っていきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Linuxのデバッガを自作したい

Linuxのデバッガを自作したい

まだ途中であるが経過を記録しようと思い記事を書いた。
今後記事を成長させていく。

方法

  • c言語を使う
  • Ubuntu64bitを使う

現状

デバッグしたい対象を子プロセスとして起動し、ブレークポイントを置けた。
対話型っぽくしてgdb感を出せた。

引っかかった点

ptraceを使ってデバッグするのだが、子プロセスを作ってデバッグする場合は、PTRACE_ATTACHによるアタッチをする必要がないみたいだ。

waitpidの使い方が現状よくわかってない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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に渡している。
③ 通常の出力を捨てる。
④ エラーの出力を通常の出力として扱うようにしている。これがあるとエラー出力は全て通常の出力になる。

  • 紹介したコマンドは上記のような処理が行われている。
  • 一文で書くと「①で結果が出力され、その結果が②によって④に渡され、④でエラー出力も通常の出力も通常出力として扱われ、③で通常の出力を捨てている。」
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

viがなくてもあわてない

Docker便利ですよね。いざコンテナで作業しようとしたとき、

$ vi
bash: vi: command not found

あっ vi がない!ってなりませんか?

そんなときどうしましょう。インストールする?とんでもない!
そんなあなたは今すぐ隣のvimmerと距離を置きましょう。

正解はemacsを使

テキストファイルのちょっとした編集を行うテクニックです。

echoとリダイレクト

標準出力を>でファイルに上書き、>>でファイルに追記します。

$ echo "文字列" > file.txt
$ echo "最終行に追記" >> file.txt

cat

catに標準入力-を渡すと複数行を書き込み。Ctl+Cで抜けます。

$ cat - > file.txt
a
b
c
^C

sed

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行目だけ置換
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 
5

CentOS 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秒だった。

スクリーンショット_2019-10-23_13-14-46.png

  • CentOS 5 では以下のように 3秒

スクリーンショット_2019-10-23_13-29-28.png

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 秒に対応する。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む