FIELD NOTES: 書を持って街へ出よう

合同会社フィールドワークス プログラマ兼代表のブログ

IPAmj明朝フォントの研究

独立行政法人情報処理推進機構IPA)より「IPAmj明朝フォント」が公開され,6万字というインパクトもあり,各所で注目されているようです。

弊社で開発している「PDF帳票開発ツール Field Reports」でもこのフォントが自由に使えるようになれば,利便性が高まりそうです。どうやったらこの6万字をフルに活用できるのか,調べてみました。

文字コレクション

IPAのプレス発表にも書いてあるとおり,このフォントは行政機関向け(もっと言えば人名用)に作成されたフォントです。

 氏名には多様な漢字が使われており、国、地方公共団体等の行政機関では、情報システムで適切に氏名を扱うために、コンピューターに標準搭載されていない文字を外字として作成するなど、正しく氏名を表記するための様々な取り組みを行ってきました。しかし、氏名を正確に表記したいという要望がある一方で、外字の作成や管理の手間などが、大きな課題となっていました。
 そこでIPAでは、経済産業省委託事業「文字情報基盤構築に関する研究開発事業」において、行政機関の情報システムで、人名等の漢字を効率的に扱う基盤のあり方について検討するとともに、各組織での共通基盤として利用可能な文字フォントの作成を行い、IPAmj明朝フォントの検証版として公開しました。

通常,メイリオヒラギノなどの汎用目的に作成されたフォントはJIS2004Adobe-Japan1などの文字コレクションにもとづいていますが,IPAmjフォントは「戸籍統一文字」「住民基本台帳ネットワークシステム統一文字」をベースとした以下の文字が収録されています。
* 戸籍統一文字(55,267字)
* 住民基本台帳ネットワーク システム統一文字(19,432字)
* JIS漢字コード(10,050字)
* 常用漢字(2,136字)

ただ,第一水準〜第四水準の文字数の合計と一致するので,「JIS漢字コード(10,050字)」はJIS 2004に対応していると考えられます。
汎用目的に使用しても特に問題はなさそうに思えます。

上記を全て包含する 漢字58712 文字 +かな+欧文文字+記号類 = 約6万字。正確には60,718字が収録されています。OpenTypeフォントの仕様上の限界が6,5536字なので,限界近くまで収録されていることがわかります。

符号化

http://ossipedia.ipa.go.jp/ipamjfont/mjmojiichiran/index.html で文字情報の一覧表を見ることができます。

58,712字の漢字のうちUnicodeが割り当てられている(「UCS実装」に○が付いている)文字は49,278字であり,残りの9,434文字はUnicodeが割り当てられていません。
その他,JISに属してる文字は「句点番号」,戸籍統一文字は「戸籍統一文字番号」,住民基本台帳ネットワークシステム統一文字は「住基ネット統一文字コード」といったようにそれぞれ文字コレクションでのコードが併記されていますが,いずれもすべてをカバーすることができません。

そこでIPAでは,"MJ"+6桁数字で表記される「MJ文字図形名」というコードを定義してグリフを管理しているようです。

グリフの呼び出し方

では,この6万字をフルに活用するためにはどうすればよいでしょうか?

Unicodeによるグリフ指定

現在のコンピュータでIPAmj明朝フォントを利用するには,Unicodeを使用するのが便利です。
ただし,Unicode 1.0時代の1文字=16ビットという素朴な仕様で利用できる範囲を超えていますので,49,278字を利用するには以下の仕様に対応したOSやアプリケーションが必要になります。

UTF16で21ビットのコードを表現するには2文字分必要になりますので,これを「サロゲートペア」と呼びます。
UCS実装済みの文字については,Unicodeで直接よびだすことができます。

異体字セレクタ(IVS)」とは,例えば渡邊の「邊」のように復数の字形を持つ文字において「基底文字+異体字セレクタ」の組み合わせでグリフを区別する方法です。IPAmj明朝フォントでは,4,193字にIVSが割り当てられています。
例えば,異体字が多いことで有名な渡邊の「邊」に対応する文字は「文字情報一覧表」に16文字あります。このうち,「IVS実装」に「○」が付いている15文字については異体字セレクタを使って区別して呼び出すことができます。
f:id:fet:20111212115840p:image:w640

GIDによるグリフ指定

UCSが未実装かつIVS実装されていない残りの7,1617,160文字を呼び出すためには,Unicode以外の方法を使う必要があります。次に考えられるのは,GIDを直接使ってグリフを指定する方法です。GIDというのは,TrueType内部でのグリフの管理番号であり,0〜65,535の整数値が割り当てられます。

ただ,GIDは内部管理番号なのでフォントにより体系が異なります。
PostScript系のCIDフォントであれば,アドビが“Adobe-Japan1”で定義したCIDにより指定することができますが,TrueType系のフォントはグリフとGIDの対応表が公開されていなければ実質使えません。また,Adobe-Japan1に準拠しているわけではないので,CIDと対応付けるのも無理な話です。

また,残念ながら「文字情報一覧表」にはGIDは記載されていません。
技術的にはGIDとグリフの一覧をダンプすることも可能ですが,検索するのが大変そうです(実用にならないでしょう)。
UCS未実装の9,434字かつIVS実装の7,160文字は実質利用することができないのでしょうか?

グリフ名によるグリフ指定

そこでIPAに問い合わせてみたところ,有力な情報を教えて頂けました。
TrueTypeフォントファイルの“post”テーブルに「グリフ名」とGIDの対応表が収納されているとのことです。

postテーブルは,PostScript系フォントの互換のために残されているテーブルという頭があったので盲点でした。
試しにpostテーブルをダンプしてみると,確かに全グリフ分のグリフ名が収録されていました。以下に,Apple社が配布している「The Apple Font Tool Suite」を使ってpostテーブルをダンプした結果の一部を示します。グリフ名はMJ文字図形名に対応した「"mj"(小文字)+6桁数字」となっています。

                <PostScriptName glyphRefID="3160" NameString="mj001194" />
                <PostScriptName glyphRefID="3161" NameString="mj001195" />
                <PostScriptName glyphRefID="3162" NameString="mj001196" />
                <PostScriptName glyphRefID="3163" NameString="mj001197" />
                <PostScriptName glyphRefID="3164" NameString="mj001198" />
                <PostScriptName glyphRefID="3165" NameString="mj001199" />
                <PostScriptName glyphRefID="3166" NameString="mj001200" />
                <PostScriptName glyphRefID="3167" NameString="mj001202" />
                <PostScriptName glyphRefID="3168" NameString="mj001203" />
                <PostScriptName glyphRefID="3169" NameString="mj001204" />
                <PostScriptName glyphRefID="3170" NameString="mj001205" />
                <PostScriptName glyphRefID="3171" NameString="mj001206" />
                <PostScriptName glyphRefID="3172" NameString="mj001207" />
                <PostScriptName glyphRefID="3173" NameString="mj001208" />
                <PostScriptName glyphRefID="3174" NameString="mj001209" />
                <PostScriptName glyphRefID="3175" NameString="mj001210" />
                <PostScriptName glyphRefID="3176" NameString="mj001211" />
                <PostScriptName glyphRefID="3177" NameString="mj001212" />
                <PostScriptName glyphRefID="3178" NameString="mj001213" />

アプリケーションにグリフ名で文字を呼び出す機能があれば,IPAmj明朝フォントのすべてのグリフを活用できそうです。

Field Reportsでの対応

以上の調査結果を元に,現在開発中のPDF帳票開発ツールField Reports 1.4では,以下の機能を盛り込む予定です。
これだけ用意しておけば,IPAmj明朝フォント以外の多くのグリフを収録したフォントでも利用できるのではないでしょうか?

  • 21ビットコードポイント(サロゲートペア)対応
  • 異体字セレクタ対応
  • GID/CIDによるグリフ指定
  • グリフ名によるグリフ指定
  • PDFへのフォントの埋込
2011.12.19 追記

Field Reports 1.4では,実体参照風の書式によりグリフ名参照ができるようになりました。

    &@<グリフ名>;

OCamlで作成した拡張モジュールでセグメンテーション違反発生

以前のブログでも書いたとおり,弊社ではOCamlを使ってLL言語用の拡張ライブラリを作成しています。
この構成でこれまでは特に問題なく稼動していたのですが,機能拡張していくうちに突然セグメンテーション違反が発生するようになってしまい困りました。
現在のところ根本的な原因は判明していないのですが,これまでの調査結果と見つかった回避策をいったんまとめておきます。

現象

セグメンテーション違反は,64ビット版Linuxのみで発生します。32ビット版LinuxMac OS Xでは発生しません。使用するLL言語によっても違いがあり,Pythonでは発生せず,PerlRubyで発生します(PHPは未確認)。
動作環境は,CentOS 5.5 です。

gdbによる解析

OCamlで作成した共有ライブラリをfoo.soとすると,その共有ライブラリを各LL言語用の拡張ライブラリfoo_py.so,foo_rb.so, foo_pl.soが利用する構造になっています。foo.soは共通なので,呼び出し方によってセグメンテーション違反が発生したりしなかったりすることになります。
違いがどこにあるのか動作をデバッガで追ってみました。

まず,セグメンテーション例外が発生する直接の原因は範囲外のメモリにアクセスしたためでした。rsiレジスタにはデータ格納領域のアドレスが格納されているはずなのですが,でたらめな値になっています。

(gdb) info reg
rax            0x930000002a95f07e     -7854277749419675522
rbx            0x2a95f05798     182904182680
rcx            0x2a95f05002     182904180738
rdx            0x7bb     1979
rsi            0x930000002a95f07e     -7854277749419675522
rdi            0x2a95c13b70     182901095280
rbp            0x2a95f057f8     0x2a95f057f8
rsp            0x7fbfffe810     0x7fbfffe810
r8             0x1     1
r9             0x1     1
r10            0x0     0
r11            0x2a95ebd630     182903887408
r12            0x2a95f06180     182904185216
r13            0x2a95f06078     182904184952
r14            0x7fbfffe9d0     548682066384
r15            0x2a95f05768     182904182632
rip            0x2a95c13b77     0x2a95c13b77 <camlField__Pdfreport__set_field_value_3874+7>
eflags         0x10202     66050
cs             0x33     51
ss             0x2b     43
ds             0x0     0
es             0x0     0
fs             0x0     0
gs             0x0     0

後は,レジスタが破壊される場所を絞り込んでいけば原因が推測できそうですが,これが一筋縄では行きませんでした。
この手のローレベルなデバッグではありがちなことですが,ブレークポイントを設定することでプログラムの動作が影響を受けてしまうようなのです。ブレークポイントを細かく設定して発生場所を絞り込んでいくと,捜査範囲外で例外が発生するなどしてなかなかうまくいきません。

ただ,試行錯誤を続けるうちになんとなく傾向が見えてきました。以下のような“XXX@plt”という形式のシンボルへサブルーチンコールした直後にレジスタが破壊されることが多いようなのです。

callq  0x2a95b2e780 <camlField__Extlib__ExtList__loop_1124@plt>

“XXX@plt”というのは,動的リンクを行う際に遅延リンクを実現するための仕組みです。PLTと呼ばれるテーブルにサブルーチンのアドレスが並んでいて,サブルーチンコールはPLTを介して間接的に行うことになります。共有ライブラリロードした直後にはPLTには実際のアドレスは格納されておらず,最初のサブルーチンコールを行った際に動的にシンボルを解決します。

動的リンクの仕組みの詳細については,参考文献を参照してください。特に「リンカ・ローダ実線開発テクニック」が非常に詳しくてお勧めです。

共有ライブラリ呼び出し側の違い

一方,Pythonで動いてRubyPerlで動かないということは,共有ライブラリの呼び出し方に何か違いがあるのかもしれません。

Rubyのソースを調べたところ,RTLD_LAZYフラグを指定して共有ライブラリをオープンしていました。

        /* Load file */
        if ((handle = (void*)dlopen(file, RTLD_LAZY|RTLD_GLOBAL)) == NULL) {
            error = dln_strerror();
            goto failed;
        }

Pythonでは,フラグの値が変数に格納されているので動作時に実際にどのモードで動くのかをソースから解析するのは困難でしたが,少なくともdlopenflagsの初期値としてはRTLD_NOWが設定されているようでした。

#if !(defined(PYOS_OS2) && defined(PYCC_GCC))
    dlopenflags = PyThreadState_GET()->interp->dlopenflags;
#endif

    handle = dlopen(pathname, dlopenflags);

RTLD_LAZYモードでは,シンボル解決が最初の呼び出し時まで遅延されます。RTLD_NOWモードでは,共有ライブラリのロード時にシンボル解決が行われます。

実際にデバッガでステップ実行すると,Rubyから起動した場合は“XXX@plt”のコール後にシンボル解決ルーチンへジャンプしますが,Pythonから起動した場合はPLTテーブルに既に有効なアドレスが格納されていてシンボル解決ルーチンへのジャンプは発生しません。

考察

遅延シンボル解決ルーチンは決して実行環境に影響を与えないように設計されているはずですが,何らかの理由により実行環境に影響を与えてしまっているのではないでしょうか?

例えば,以下のような理由が考えられます。

  • OCamlが出力するコードが,遅延シンボル解決利用時の規約に違反している。
  • 共有ライブラリが,シンボル解決ルーチンの想定を超える構造になっている(例えば,シンボル数が多すぎるなど)。

ただ,遅延シンボル解決を利用する際の規約のようなものが存在するのかどうか分かりませんし,私自身のマシン語の知識が8ビット時代で止まっているので,マシン語コードがなにをやっているのか深く理解するのが困難な状況です。これ以上の深追いができなくて,調査が中断しているというのが実情です。

ちなみに,64ビット版のみで問題が発生する理由は,OCamlが出力するアセンブラコードの性質が異なるためです。32ビット版では遅延シンボル解決に対応した“XXX@plt”形式のシンボルを出力しません。

回避策

共有ライブラリロード時にRTLD_NOWモードで開かせることができれば,回避することができます。
幸い,環境変数LD_BIND_NOWにより遅延シンボル解決を抑制することができます。

$ export LD_BIND_NOW=1

この方法では他の共有ライブラリにも影響を与えてしまいますが,共有ライブラリを作成する際にリンカに“-z now”オプションを与えれば,遅延シンボル解決を抑制する対象を絞り込むことができます。

$ gcc -shared -Wl,-z,now -o foo.so foo.o

“-z now”オプションを付ける方法で,しばらく様子を見てみようと思います。

参考文献

  • 坂井弘亮 著, 「リンカ・ローダ実線開発テクニック」, CQ出版, 2010年
  • 高林哲 他著, 「Binary Hacks ―ハッカー秘伝のテクニック100選」, オーム社, 2006年
  • John R. Levine 著, 榊原一矢 監訳, 「Linkers & Loaders」, オーム社, 2001年
  • 青木峰郎 著, 「ふつうのコンパイラをつくろう」, ソフトバンク クリエイティブ, 2009年

現在契約できるプロバイダフリーのIP電話

ナンバー 電話 ビジネス  - 写真素材
(c) nakusa123画像素材 PIXTA

現在,050 plusのようなスマートフォンから利用できるIP電話サービスが開始され好評の様ですが,使用する端末やアプリが限定されてしまうのがちょっと不満に思えます。一方,各ISPがひかり電話を始めとするIP電話サービスを提供していますが,この場合は使用する回線が限定されてしまいます。

好みのIP電話アプリや,自宅サーバにインストールしたAsteriskなどのSIPサーバに自由に登録して使用することはできないものでしょうか?

このような制限のないIP電話サービスは「プロバイダフリーのIP電話サービス」と呼ばれ,海外にはたくさん存在します。日本でも2005年前後にいくつか存在したのですが,規制か何かの影響ですっかり廃れてしまいました。

それから数年経って,またボチボチとサービスが立ち上がってきたようですので,現在契約可能なプロバイダフリーのIP電話を調べて見ました。

選定の条件は,以下のとおりです。

  • 使用回線が限定されない(プロバイダフリー)。
  • 標準的なIP電話プロトコル(SIP等)に準拠していて,専用アプリケーションを必要としない。
  • 日本の電話番号が取得できる。
  • 個人で契約できる。

どこでもIPフォン

URL:http://www.ip-denwa.com/
電話番号:03-xxxx-yyyy
月額基本料金:1,000円
通話料金:(固定電話)8.4円/3分,(携帯電話)18.27円/分
その他の特長:内線通話,転送電話設定可能

FleaLine Light

URL:http://www.covia.jp/net/flealine01.html
電話番号:050-xxxx-7777 または 03-xxxx-yyyy(オプション)
月額基本料金:400円
通話料金:(固定電話)8.4円/3分,(携帯電話)16.8円/分
その他の特長:転送電話設定可能(オプション)

Phytter

URL:http://www.phytter.com/
電話番号:050-xxxx-7777 または 03-xxxx-eye
月額基本料金:5ドル/月
通話料金:(固定電話)3セント/分,(携帯電話)15セント/分
その他の特長:PayPalでドル決済

まとめ

以上,条件に合ったIP電話サービスが3件見つかりました。

試しにPhytterに加入しiPhone上のAcrobits SoftPhoneに登録してみましたが,問題なく発信できました。

VMware ESXi 5.0でVNC接続を利用する方法

最近,VMware ESXi を5.0に移行したところ,今まで使えていたVNCでの接続ができなくなってしまいました。

VMWareサーバにログインしてログファイルを調べてみたところ,以下のようなメッセージを発見。

vmauthd.log:2011-09-05T14:47:33Z vmauthd[11156]: vthread-3| VMAuthdSocketWrite: writing: 220 VMware Authentication Daemon Version 1.10: SSL Required, ServerDaemonProtocol:SOAP, MKSDisplayProtocol:VNC , VMXARGS supported

どうやら,SSH経由でVNC接続する必要があるようです。

VMware ESXiでのVNC接続を有効にする方法

以下の記事などを参考にVNC接続を有効にする設定を行ってください。

なお,*.vmxを直接編集しなくても,VMware vSphere Clientの「仮想マシンのプロパティ/オプション/詳細/全般/構成パラメータ…」からパラメータを変更することができます。

SSHのポートフォワーディング機能を使う方法

ターミナルから以下のコマンドを実行します。

$ ssh -L <ローカルホストポート>:localhost:<リモートホストポート> <ユーザ名>@<VMwareサーバ名/アドレス>

ここで,リモートホストポートは,各仮想マシンに割り当てたポート番号です(通常,5900以上)。
ローカルホストポートは任意のポート番号で良いのですが,私はリモートホストポートと同じ番号にしています。
ユーザ名は,VMwareサバーへログインできるユーザ名です(通常“root”?)。

SSH Tunel Managerを使う方法(Mac OS X専用)

SSH Tunel Managerをインストールして,上記のSSHと同様の設定を行います。
f:id:fet:20110905154513p:image

設定が終わったらウインドウを閉じて「再生」ボタンを押せば,SSHトンネリングが確立します。

VNCでの接続

これで,お好みのVNCクライアントを使って接続できるはずですが,接続先ホスト名が“localhost”になることに注意してください。

ちなみに,Mac OS Xの場合,OS標準の「画面共有」を使ってVNC接続できます。
Finderから「移動/サーバへ接続…」を選択して,「サーバアドレス:」に以下のように入力してください。

vnc://localhost:<ポート番号>/