Mac bookにログインできなくなった

OS X 10.9.4 でフリーズ後再起動するとログイン出来なくなった。ユーザー選択して、パスワード入力画面まではいけるのだがパスワード入力後、虹色のぐるぐるが延々終わらずにフリーズしている模様。

セーフモード起動試してみるも同じ。ディスクユーティリティ起動してディスクのチェックしても問題なし。
シングルユーザーモードで起動後、ログを確認してみると以下のようなエラーが記録されていた。
どうもsystemkeychainがhangしていて認証途中で止まっている模様

# less /var/log/system.log  
Apr 12 00:00:22 --- last message repeated 5 times ---
Apr 12 00:00:22  systemkeychain[324]: Could not run security-checksystem failure Resource temporarily unavailable
Apr 12 00:00:22 host com.apple.launchd[1] (com.apple.systemkeychain[324]): Exited with code: 71
Apr 12 00:00:22 host.local ReportCrash[313]: Saved crash report for bash[325] version ??? to /Library/Logs/DiagnosticReports/bash_2018-04-12-000022_host.crash
Apr 12 00:00:22 host com.apple.launchd[1] (com.apple.systemkeychain): Throttling respawn: Will start in 10 seconds
Apr 12 00:00:22 host.local ReportCrash[313]: Removing excessive log: file:///Library/Logs/DiagnosticReports/bash_2018-04-11-235700_host.crash
Apr 12 00:00:32 host.local ReportCrash[328]: Attempt to read info_array in pid 327 failed, address was ...

crash ログを見てみるとlibreadline.6.dylibが無いとかでエラーを吐いている。

# less  /Library/Logs/DiagnosticReports/bash_2018-04-11-235700_host.crash
Dyld Error Message:
  Library not loaded: /usr/local/opt/readline/lib/libreadline.6.dylib
  Referenced from: /bin/bash
  Reason: image not found

確認してみると確かにlibreadline.6.dylibは存在しない。消した記憶はないのだけど、どうもbrew経由でなにかをinstallしたときに7に勝手に揚げられていた模様。

ls /usr/local/opt/readline/lib/ 
libhistory.7.dylib    libhistory.dylib      libreadline.7.0.dylib libreadline.a
libhistory.7.0.dylib  libhistory.a           libreadline.7.dylib   libreadline.dylib

ためしに/をwritableに再マウントしてsymbolic link貼ってやると起動できた。

mount -uw / 
cd /usr/local/opt/readline/lib/ 
ln -s libreadline.dylib libreadline.6.dylib
ln -s libhistory.dylib  libhistory.6.dylib 
reboot

apacheでhttps通信をできるようにする

https通信を行うようにするには電子証明書が必要となる。電子証明書を作成するには証明書署名要求 (CSR: certificate signing request)を作成し、公開鍵証明書認証局(CA:Certificate Authority)にて署名した証明書を発行してもらわなければならない。今回は簡単なテスト用なので自己署名証明書(いわゆるオレオレ証明書)を利用する。そのためまずは署名するための認証局を作成する。実験環境はubuntu12.10。

CAの作成

今回はopensslを利用する.以下にてmake & install.

$ wget http://www.openssl.org/source/openssl-1.0.1c.tar.gz
$ tar xzvf openssl-1.0.1c.tar.gz
$ cd openssl-1.0.1c
$ ./config
$ make && make install

次に認証局を作成するための場所と設定ファイルのコピー。

# mkdir /etc/ssl
# cp openssl-1.0.1c/apps/openssl.cnf /etc/ssl # /usr/lib/ssl/openssl.cnfから/etc/ssl/openssl.cnfにシンボリックリンクが貼られていたのでここにコピーした。ここは環境によって適当に読み替える。
# cd /etc/ssl

実際に認証局を作成していく。openssl付属のCA.shなどのスクリプトを利用すると簡単につくってくれる。

# /usr/local/ssl/misc/CA.sh -newca
CA certificate filename (or enter to create)

Making CA certificate ...
Generating a 1024 bit RSA private key
..............................++++++
........................................................................++++++
writing new private key to './demoCA/private/./cakey.pem'
Enter PEM pass phrase: # 認証局の秘密鍵用のパスフレーズを入力
Verifying - Enter PEM pass phrase: # 認証局の秘密鍵用のパスフレーズを再入力
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:JP  #国名を入力
State or Province Name (full name) [Some-State]:Tokyo # 市町村を入力
Locality Name (eg, city) []:shinjyuku # 区などを入力
Organization Name (eg, company) [Internet Widgits Pty Ltd]:foo # 会社名などを入力
Organizational Unit Name (eg, section) []:developer group 1 # 部署名などを入力
Common Name (e.g. server FQDN or YOUR name) []:hoge.foo.com # FQDN
Email Address []:hoge@foo.com # 連絡先を入力

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:  #何も入力せずenter
An optional company name []: #何も入力せずenter
Using configuration from /usr/lib/ssl/openssl.cnf
Enter pass phrase for ./demoCA/private/./cakey.pem:  # 認証局の秘密鍵用のパスフレーズを再入力
Check that the request matches the signature
Signature ok
Certificate Details:
....

Write out database with 1 new entries
Data Base Updated

これで認証局の作成が完了した。

CSRの作成

次にhttps通信に利用する証明書を作成していく。証明書を作成するためにWebサーバー側で行うのはCSRの作成である。Webサーバーと認証局を同一のサーバー上で行っているのでいまどっちの作業を行っているかわかりにくい。イメージとして、Webサーバーと認証局を構築しているサーバーは別サーバーで行っていると読み替えた方がイメージは湧きやすいと思う。
CSRにはWebサーバーの秘密鍵と検証用のハッシュ値が含まれる。まずCSRに添付する秘密鍵を作成する。

# openssl genrsa -des3 -out server.key 1024
....
Enter pass phrase for server.key: # CSRに含めるサーバーの秘密鍵のパスフレーズを入力
Verifying - Enter pass phrase for server.key # 確認用に再入力

このままだとWebサーバーの起動毎にパスフレーズの入力を求められるのでプロテクトを解除する

# openssl rsa -in server.key -out server.key
Enter pass phrase for server.key: # 先ほど入力したものを同じものを入力
writing RSA key

次にCSRを作成する

# openssl req -new -days 365 -key server.key -out server.csr
ここで入力を求められるがcountryName/stateOrProvinceName/organizationNameは認証局と同じものを設定しないと署名ができないので注意。

これでCSRが完成した。

証明書の作成

証明書を作成するには先ほど作成したWebサーバーのCSRにCAが署名すればよい。署名は以下のようにする

# openssl ca -in server.csr -keyfile demoCA/private/cakey.pem -out server.crt
ここで入力するパスフレーズは認証局のパスフレーズ値

これで証明書が作成できた。

apacheの設定

sslの設定で適切な場所に秘密鍵と証明書をおく

$ cat /etc/apache2/sites-enabled/httpd-ssl.conf
....
#   Server Certificate:
#   Point SSLCertificateFile at a PEM encoded certificate.  If
#   the certificate is encrypted, then you will be prompted for a
#   pass phrase.  Note that a kill -HUP will prompt again.  Keep
#   in mind that if you have both an RSA and a DSA certificate you
#   can configure both in parallel (to also allow the use of DSA
#   ciphers, etc.)
SSLCertificateFile "/etc/apache2/server.crt" <-- ここにwebサーバーの証明書をおく
#SSLCertificateFile "/etc/apache2/server-dsa.crt"

#   Server Private Key:
#   If the key is not combined with the certificate, use this
#   directive to point at the key file.  Keep in mind that if
#   you've both a RSA and a DSA private key you can configure
#   both in parallel (to also allow the use of DSA ciphers, etc.)
SSLCertificateKeyFile "/etc/apache2/server.key" <-- ここにwebサーバーの秘密鍵をおく
#SSLCertificateKeyFile "/etc/apache2/server-dsa.key"
....
# apache2ctl restart

以上でwebサーバーにhttpsアクセスできるようになるはず。

ブラウザインポート

アクセスするたびに警告がでるため、認証局をブラウザに信頼できる認証局と認識させる。

# openssl x509 -inform pem -in /etc/ssl/demoCA/cacert.pem -outform der -out ca.der

作成したca.derをブラウザインポートすると警告がでなくなる。

問題C. ビット数

本番中はlarge解けなかった。考え直したら解けたっぽいのでメモ書き残す。
aとbを二進数表記したときのビットをそれぞれAn, Bnと表し、それぞれの
桁のCarryをCnと表す。Nを二進数表記したものをNnと表す。
このときan+bn+Cn=NnをAnとBnがなるべく多く立っているように満たす組み合わせでカウントする。

     An ... A2 A1 A0
+) Bn ... B2 B1 B0
+) Cn ... C2 C1  C0
-----------------
    Nn ... N2 N1  N0

方針としてCarryがない場合とある場合で考える。

Carryがない場合(Cn=0))

(a) Nnが0のときの組み合わせ候補は(An, Bn, Cn) = {(0, 0, 0), (1, 1, 0)}である。最も多くビットが立っているAnとBnの組み合わせは(1,1,0)が選択され、立っているビット数は+2される。さらにこのときCarryが立つ。
(b) Nnが1のときの組み合わせ候補は(An, Bn, Cn) = {(1, 0, 0), (0, 1, 0)}である。どちらの組み合わせを選んでもよい.立っているビット数は+1.

Carryがある場合(Cn=1))

(a)Nnが0のときの組み合わせ候補は(An, Bn, Cn) = {(1, 0, 1), (0, 1, 1)}である。どちらの組み合わせを選んでもよい.立っているビット数は+1.このときCarryは立つ。
(b)
(b-1. Nnが最上位ビットでない場合)
Nnが1のときの組み合わせ候補は(An, Bn, Cn) = {(0, 0, 1), (1, 1, 1)}である。
最も多くビットが立っているAnとBnの組み合わせとして(1,1,1)が選択され、立っているビット数は+2される。さらにこのときCarryが立つ。
(b-2. Nnが最上位ビットの場合)
Nnが1のときの組み合わせ候補は(An, Bn, Cn) = {(0, 0, 1)}である。最上位ビットなのでCarryが立てられないためである。よって立っているビット数+0.

具体例としてN=5のケースで考えてみる。

     a2 a1 a0
+) b2 b1 b0
+)    ?   ?    0
-----------------
      1   0    1

Nを下位ビットからひとつずつみていく。n0=1となる組み合わせは(a0, b0, c0) = {(1, 0, 0), (0, 1, 0)}なのでbit0の最も多く立っているビット数は1.このときはCarryは発生しないのでc1=0となる。

     a2 a1 a0
+) b2 b1 b0
+)    ?   0   0
-----------------
      1   0    1

n1=0となる組み合わせは(a1, b1, c1) = {(1, 1, 0)}なのでbit1の最も多く立っているビット数は2.
このときCarryが発生するため、c2=1となる。

     a2 a1 a0
+) b2 b1 b0
+)    1   0   0
-----------------
      1   0    1

n2 = 1となる組み合わせは(a2, b2, c2) = {( 0, 0, 1), (1, 1, 1)}である。通常であれば、最も多く立っているビット数は2.ただし最上位ビットなので(1,1,1)の組み合わせは存在しない。よって(0, 0, 1) なので最も多く立っているビット数は0となる。

以上から各桁に関して最も多く立っているビット数の和は1+2+0=3

上記の内容を愚直に書いたこーど

#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>

using namespace std;
typedef long long ll;

int solve(ll N) 
{
    int ret = 0;
    bool carry = false;
    
    while(N) {
        int x;
        if(carry) {
            if(N & 1) { x = 2; carry = true; }
            else      { x = 1; carry = true; }
        } else {
            if(N & 1) { x = 1; }
            else      { x = 2; carry = true; }
        }
        ret += x; 
        N >>= 1;
    }
    if(carry) ret -= 2;

    return ret;
}

int main()
{
    int T;

    scanf("%d", &T);
    for(int i = 1; i <= T; i++) {
        ll N;
        scanf("%lld", &N);
        cout << "Case #" << i << ": " << solve(N) << endl;

    }

}

この一年間で読んでよかったと思った本

年度明けたし、昨年度読んでよかった本をまとめてみる。


Unixシステムコールの使い方や仕組みを丁寧にわかりやすく解説した本。文章がとてもわかりやすく理解しやすい。記述が古い部分があったり、pthreadは載ってない。その辺気になる人は原著のほうを読むとよいかも。著者の方は亡くなられてるのでpthreadのとこは別の人が書いてるので注意。


就職してから組み込みとかやるようになったんだけど、学生のときは全然やったことなくてさっぱりわからんときに読んだ本。インターフェースってどういったもので、どういう風にデータをやりとりしているのかといった考え方や仕組みを学べた。


とても簡単な組み込み入門用の本。最初はLED光らせることから初めて、最後はリモコンの赤外線受信をやってる。うすい本でコード自体も短くわかりやすいが、組み込みの基本的な考え方を学べる。実際に手を動かしてみるのがよい。


自作OS作ってみたい人向け。前半はブートローダー、後半はOS作ってる。信号線操作したり、レジスタ読み書きしたりなどハードに近い部分を制御しているのでデータをどうやってやりとりしているのかといったことがイメージしやすいと思う。ちなみにPC->H8への転送用にXMODEMまで実装しているので通信プログラムまで学べる。会社の新人研修の題材とかにもいいんじゃないでしょうか。


組み込みOSとおなじ著者の方の本。リンカって普段あまり意識しないけど実際にはなにをやっていてどういう仕組みなのか知りたい人向け。あと、組み込みOSの本でもリンカ使う場面あるんだけど、説明が少ないのでなにをやっているのか理解したい人はこっちもセットで読むといいかも。


プログラミングコンテストを題材としたアルゴリズム本。実践的かつ理論も説明した稀有な本。愚直に考えると複雑なコードになりそうな問題を、発想の転換で簡潔なコードで表現しているのは感動する。


Linuxのコマンド実装していくことを通してLinuxの仕組みを学べる本。最初はwhoコマンド実装から始まり最後のほうは簡易Webサーバーまで実装している。詳解UNIXとともにおすすめ。


Linuxカーネル読んでみたいけど何から手をつけたらいいかわからん人(僕とか)の最初のとっかかりにはわかりやすくていい本だと思う。


わかりやすいLinuxデバイスドライバ作成入門書。割といろいろなデバイスの説明があり、組み込みインターフェースの知識を得るのにも使える。なぜかIPMIがあるのが面白いw


一般的なCPUの仕組みから、仮想化支援機構まで網羅した本。コンピューターの仕組みを学ぶための新人研修の教科書とかにいいと思う。


IT業界で働いてるor働く人には楽しめると思う。

就職してから割と豪快に本を買えるようになったのでいい本読めたかなーと思う。今年度は電気やアナログ/デジタル回路を勉強したいなぁ。

ぼっちリーディング(1)

カーネル読んでみたいなーと思ったので読んでみる。対象は手元に転がっていたFreeBSD

$uname -a
FreeBSD foo.localdomain 8.0-RELEASE FreeBSD 8.0-RELEASE #0: Sat Nov 21 15:48:17 UTC 2009     root@almeida.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC  i386

まったく経験ないので、とりあえず簡単なお題を設定して読み解くスタイルで進めることにする。お題は、dmesgで表示されるCPU名はどうやってとってきているんだろう?に決定。CPU名ってのはこれのことね。

dmesg | grep -i cpu
CPU: Intel(R) Core(TM)2 Duo CPU     P8600  @ 2.40GHz (2388.76-MHz 686-class CPU)
cpu0: <ACPI CPU> on acpi0
acpi_throttle0: <ACPI CPU Throttling> on cpu0

とりあえずmanでもみてみますかね。

$ man dmesg
DESCRIPTION
     The dmesg utility displays the contents of the system message buffer.  If
     the -M option is not specified, the buffer is read from the currently
     running kernel via the sysctl(3) interface. 

あー、sysctl(3)経由で値を取得してるみたいだ。ソースのぞいてみる。FreeBSDの場合、カーネルソースは/usr/src/sys以下にある。sys/sysctl.hをのぞいてみる。つらつらと眺めてみたが、それっぽいのを見つけられなかった。ちなみに実体は、kern/kern_sysctl.cっぽい。しばし考えて、printされてる文字列で検索かければいいんじゃね?と思いつく。

$ grep -R "CPU: " *
    :
dev/mly/mly.c:	                mly_printf(sc, "CPU: %s @ %dMHZ\n", 
i386/i386/identcpu.c:	printf("CPU: ");
ia64/ia64/machdep.c:	printf("CPU: %s (", model_name);
    :

なんかそれっぽいのがひっかかった。i386/i386/identcpu.cをのぞいてみる。printfの該当箇所は以下の関数。

void
printcpuinfo(void)
{
        u_int regs[4], i;
        char *brand;

        cpu_class = i386_cpus[cpu].cpu_class;
        printf("CPU: ");
        strncpy(cpu_model, i386_cpus[cpu].cpu_name, sizeof (cpu_model));

        /* Check for extended CPUID information and a processor name. */
        init_exthigh();
        if (cpu_exthigh >= 0x80000004) {
                brand = cpu_brand;
                for (i = 0x80000002; i < 0x80000005; i++) {
                        do_cpuid(i, regs);
                        memcpy(brand, regs, sizeof(regs));
                        brand += sizeof(regs);
                }
        }
                :

init_exthigh()が気になる感じ。中身はこんなん。

static void
init_exthigh(void)
{
        static int done = 0;
        u_int regs[4];

        if (done == 0) {
                if (cpu_high > 0 &&
                    (cpu_vendor_id == CPU_VENDOR_INTEL ||
                    cpu_vendor_id == CPU_VENDOR_AMD ||
                    cpu_vendor_id == CPU_VENDOR_TRANSMETA ||
                    cpu_vendor_id == CPU_VENDOR_CENTAUR ||
                    cpu_vendor_id == CPU_VENDOR_NSC)) {
                        do_cpuid(0x80000000, regs);
                        if (regs[0] >= 0x80000000)
                                cpu_exthigh = regs[0];
                }

                done = 1;
        }
}

regsはたぶんレジスタを表現してんだろーな。do_cpuidが怪しい感じですね。do_cpuidはsys/i386/include/cpufunc.hで定義されていてこんな感じ。

static __inline void
do_cpuid(u_int ax, u_int *p)
{
        __asm __volatile("cpuid"
                         : "=a" (p[0]), "=b" (p[1]), "=c" (p[2]), "=d" (p[3])
                         :  "0" (ax));
}

インラインアセンブラですな。cpuid命令とやらをつかっているらしい。簡単にいうとEAXレジスタの特定の値セットして、cpuid命令をcallすればcpuの名前とかがレジスタに格納されるらしい。これを使ってCPU情報とってきてるんだねー。init_exthighの中ではdo_cpuid(0x80000000, regs);の形で呼び出しているのでサポートする最大拡張機能番号の取得を行ってる。最大拡張機能番号っつーのは、EAXに設定できる最大の番号と思えばよい。Wikipediaによると0x80000008がいまのとこ最大。(Intel developers manualで要確認)。これらを踏まえてprintcpuinfoを見直すと、for (i = 0x80000002; i < 0x80000005; i++) { … } の中でEAXの値を0x80000002から0x80000004まで変更しながらcpuid命令を実行しながらプロセッサブランド文字列を取得しているのがわかる。んで、最終的に出力しているのは下記の部分。

        :
        /*
         * Replace cpu_model with cpu_brand minus leading spaces if
         * we have one.
         */
        brand = cpu_brand;
        while (*brand == ' ')
                ++brand;
        if (*brand != '\0')
                strcpy(cpu_model, brand);

        printf("%s (", cpu_model);
        switch(cpu_class) {
        case CPUCLASS_286:
                printf("286");
                break;
        case CPUCLASS_386:
                printf("386");
                break;
#if defined(I486_CPU)
        case CPUCLASS_486:
                printf("486");
                bzero_vector = i486_bzero;
                break;
#endif
#if defined(I586_CPU)
        case CPUCLASS_586:
                hw_clockrate = (tsc_freq + 5000) / 1000000;
                printf("%jd.%02d-MHz ",
                       (intmax_t)(tsc_freq + 4999) / 1000000,
                       (u_int)((tsc_freq + 4999) / 10000) % 100);
                printf("586");
                break;
#endif
#if defined(I686_CPU)  // 今回はこのCPUタイプ
        case CPUCLASS_686:
                hw_clockrate = (tsc_freq + 5000) / 1000000;
                printf("%jd.%02d-MHz ",
                       (intmax_t)(tsc_freq + 4999) / 1000000,
                       (u_int)((tsc_freq + 4999) / 10000) % 100);
                printf("686");
                break;
#endif
        default:
                printf("Unknown");      /* will panic below... */
        }
        printf("-class CPU)\n");
        :

cpu0: on acpi0とかacpi_throttle0: on cpu0はどこから出力してんのかわかんなかった。まー、とりあえずCPU情報取得するにはインラインアセンブラ使ってcpuid命令をcallすればいいというのがわかったのでよしとしよう。あと結局dmesgからどうやって読んでいるのかこれだけだとつながってないな。

組み込みOS自作入門勉強会(4)

この日は3章を読んだ。メインはROMとRAMの違いとメモリ上の領域の話かな。

ROMとRAM

ROMは読み込み専用でプログラム側からは書き込みできないけど、電源OFFしても内容が残る。RAMはプログラム側から値を読み書きできるが、電源OFFすると値は消える。ROMは種類がある。例えば、特別な操作で内容の書き換えが可能なフラッシュROMがある。なので、基本的なデータ(ex.ブートローダ)などをフラッシュROMに書き込んでおき、電源ONになるとRAM上にデータコピーするなどして値を取り扱う。

メモリ上の領域

CPUがプログラムを実行するには、実行形式ファイルの内容をメモリ上にコピーして読み出す必要がある。ベタにコピーするのではなく一般に以下の領域に決められた条件で展開される。

領域名 レジスタ
テキスト領域 CPUが実行する機械語コードがおかれる
データ領域 初期値を持つ静的変数などがおかれる
BSS領域 初期値を持たない静的変数などがおかれる

静的変数というのは、自動変数でないもの。例えば、関数の外で定義されたものやstaticがついたもの。静的変数はメモリ上に常に存在し続ける。その領域を静的領域と呼ぶ。静的領域=データ領域+BSS領域。一方、自動変数は関数内などで定義されて関数を抜けると消滅する変数。スタックに積まれる。

組み込みOS自作入門勉強会(3)

今日は2章を読んだ。制御を行うICチップをコントローラと呼ぶ。コントローラの制御はレジスタ経由で行う。たとえば、シリアルコントローラならばコントローラの持つ特定のレジスタに1byte書き込むと、コントローラ側でその1byteをシリアル通信してくれるなど。コントローラもメモリと同様にアドレス線をもっている。コントローラのレジスタアドレス空間上に存在させ、そのアドレスを読み書きすることでレジスタ操作をすることをメモリマップドI/Oと呼ぶ。組み込みだとvolatileが重要。たとえば以下のコード。

reg1 = 0x01;
reg1 = 0x02;
reg2 = 0x04;

最適化を抑制しないと、reg1 = 0x01;の書き込みは省略される。また、reg1 と reg2 の順番が逆に実行される可能性もある(パイプライン操作の関係で)。メモリ操作であれば、問題はないがレジスタ操作の場合は問題がおこる。コンパイラ側はメモリを操作しているのか、レジスタを操作しているのか理解できないのでvolatileキーワードで明示する。

この章のメインの話はメモリマップドI/Oかな。メモリ操作でレジスタ触れるよーというのがメインで、後はそれを利用した値の設定などなど。