2013/11/26(Tue)
○[386BSD(の子孫)ソースコードシリーズ (334)][な阪関無] sys/cdefs.hとは何ですか? (その11)
@前回まで
前回は RCSに含まれる ident(1)や SCCSの what(1)で、ソースコードからバージョン履歴メタデータを抽出する方法を解説しました。
@バージョン履歴メタデータはいたるところに
RCSIDやSCCSIDってのは埋められるのは別にCのソースファイルだけでなく、etc以下の設定ファイルなんかにも入ってます。
$ head -n 5 /etc/rc.conf
# $NetBSD: rc.conf,v 1.96 2000/10/14 17:01:29 wiz Exp $
#
# see rc.conf(5) for more information.
#
# Use program=YES to enable program, NO to disable it. program_flags are
もちろんシェルスクリプトにも。
$ head -n 5 /etc/rc.d/sshd
#!/bin/sh
#
# $NetBSD: sshd,v 1.21 2011/07/25 03:04:23 christos Exp $
#
つーか kernel やlibc そして実行ファイルなどバイナリにすら埋め込まれてます。
$ ident /netbsd | head -n 5
/netbsd:
$NetBSD: GENERIC,v 1.348.2.6 2012/08/15 15:32:59 sborrill Exp $
$Revision: 1.348.2.6 $
$NetBSD: std.amd64,v 1.7 2008/12/11 05:42:18 alc Exp $
$NetBSD: std,v 1.14 2011/11/22 21:25:42 tls Exp $
$ ident /lib/libc.so.12.181 | head -n 5
/lib/libc.so.12.181:
$NetBSD: crti.S,v 1.1 2010/08/07 18:01:35 joerg Exp $
$NetBSD: crtbegin.S,v 1.2 2010/11/30 18:37:59 joerg Exp $
$NetBSD: yperr_string.c,v 1.7 2005/11/29 03:12:01 christos Exp $
$NetBSD: yp_master.c,v 1.13 2003/12/10 12:06:25 agc Exp $
$ ident /bin/ksh | head -n 5
/bin/ksh:
$NetBSD: crt0.S,v 1.3 2011/07/01 02:59:05 joerg Exp $
$NetBSD: crt0-common.c,v 1.7 2011/06/30 20:07:35 matt Exp $
$NetBSD: crti.S,v 1.1 2010/08/07 18:01:35 joerg Exp $
$NetBSD: crtbegin.S,v 1.2 2010/11/30 18:37:59 joerg Exp $
このメタデータは主にバグ報告と現象の再現に、そして原因の特定に役に立ちます、例えば NetBSD の バグレポートには
Environment (output of "uname -a" on the problem machine):
という欄がありますが
- 報告対象がリリース版である
- カーネル及びユーザランドがすべてヴァニラ(ゲリラ、スリラ、連れてってマニラ*1)な配布物である
なら uname(1) の結果だけでどのソースがどのバージョンか自明なんですが、CVS HEAD なんかだと変更が激しいわけで、バグを特定するのにこれらの情報を送りつけてやると解決が早いです(バグが直るとは言っていない)。
@すごいな、(バイナリにメタデータ埋め込むの)どうやったんだ?
これはindent(1)のマニュアルに「コンパイル済バイナリにもメタデータ埋め込もう(提案)」ちゅーコード例が書いてあります。
ident works on text files as well as object files and dumps. For exam- ple, if the C program in f.c contains #include <stdio.h> static char const rcsid[] = "$Id: f.c,v $"; int main() { return printf("%s\n", rcsid) == EOF; }
@そんなのlint(1)が許さない
しかしですね、↑の例だとrcsidをprintf(3)の引数にしてるから問題ないのですが、ふつーこんなメタデータはプログラムで使い道はないわけで。
どこでも使われないままだと その6で解説した lint(1) が、この 変数rcsid はどこでも使われてないっすねと警告を出します。
$ cat >test.c
static char const rcsid[] = "$Id$";
int
main()
{
return 0;
}
^D
$ lint test.c
test.c:
test.c(1): warning: static variable rcsid unused [226]
Lint pass2:
$
観葉植物静的ソース解析ツールくんは全てを見ていた!
しゃーないので
#if defined(lint)
static char const rcsid[] = "$Id$";
#endif /*!lint*/
とlint(1)除けでもしておくしかないっすね、これはマニュアルにもそう注意があります。
If a C program defines a string like rcsid above but does not use it, lint(1) may complain, and some C compilers will optimize away the string. The most reliable solution is to have the program use the rcsid string, as shown in the example above
ただしこれでも最適化かけると消えてなくなる問題はどーにもなりません、実際 gcc -O しただけでメタデータ消えてしまいますな。
$ cat >test.c
#include <sys/cdefs.h>
static const char *rcsid = "$MyId: test.c,v $";
int
main()
{
return 0;
}
^D
[最適化なし]
$ gcc a.c
$ ident a.out | grep MyId
$MyId: test.c,v $
[最適化あり]
$ gcc -O a.c
$ ident a.out | grep MyId | wc -l
0
まぁこんなもんです。
@バークレー時代の SCCSID の宣言方法
この頃は lint(1) 除けだけしか対策してません。
例えば これCライブラリ関数のabort(3)のコードですが、indent(1)のマニュアル例と同様に *2バイナリにメタデータを埋め込んでます。
#if defined(LIBC_SCCS) && !defined(lint)
static char sccsid[] = "@(#)abort.c 8.1 (Berkeley) 6/4/93";
#endif /* LIBC_SCCS and not lint */
同様に こっちはシステムコールのbrk(2)のコードで、GNU asの.asciz疑似命令でメタデータ埋めとります。
#if defined(SYSLIBC_SCCS) && !defined(lint)
.asciz "@(#)brk.s 8.1 (Berkeley) 6/4/93"
#endif /* SYSLIBC_SCCS and not lint */
んでLIBC_SCCS/SYSLIBC_SCCS マクロは Makefile 中でON/OFFしてます、デフォルトはONです。
# All library objects contain sccsid strings by default; they may be
# excluded as a space-saving measure. To produce a library that does
# not contain these strings, delete -DLIBC_SCCS and -DSYSLIBC_SCCS
# from CFLAGS below. To remove these strings from just the system call
# stubs, remove just -DSYSLIBC_SCCS from CFLAGS.
LIB=c
CFLAGS+=-DLIBC_SCCS -DSYSLIBC_SCCS
これはバイナリサイズ減らしたいから余計なメタデータ要らねーって時にセットするわけです。
@NetBSDでのメタデータ宣言方法
前回も説明した通り、NetBSDはバージョン管理ツールにCVSを採用したので 新スタイル導入しました。
#if defined(LIBC_SCCS) && !defined(lint)
#if 0
static char *sccsid = "from: @(#)abort.c 5.11 (Berkeley) 2/23/91";
#else
static char *rcsid = "$NetBSD: abort.c,v 1.6 1995/12/28 08:51:57 thorpej Exp $";
#endif
#endif /* LIBC_SCCS and not lint */
ただsccsidをif 0して、rcsidを追加しただけなんですけどね。
そして毎回毎回
static const char *rcsid = "$NetBSD$";
と書くのがめんどくさくなったのでマクロを導入します。
@__IDSTRING(), __RCSID() マクロ
もうほんとただマクロにしただけ、 これ。
#define __IDSTRING(name,string) \
static const char name[] __attribute__((__unused__)) = string
#ifndef __RCSID
#define __RCSID(s) __IDSTRING(rcsid,s)
#endif
gccの未使用変数警告を消すために__attribute__((unused))を追加したくらいしか何もありません、単純にタイプ量が減っただけです。
またこの実装だとヘッダファイルにメタデータを埋め込むことができません。
#ifndef HOGE_H_
#define HOGE_H_
#include <sys/cdefs.h>
__RCSID("$NetBSD: hoge.h,v $");
#include <stdio.h>
static __inline void
hello()
{
puts("hello");
}
#endif /*HOGE_H*/
とヘッダ用のRCSIDを宣言し
#include <sys/cdefs.h>
__RCSID("$NetBSD: fuga.c,v $");
# include "hoge.h"
int
main(void)
{
hello();
return 0;
}
さらにCソースでRCSIDを宣言するとstatic const char *rcsidの重複定義になってしまいます。
$ gcc fuga.c
In file included from fuga.c:3:0:
hoge.h:4:20: error: redefinition of 'rcsid'
fuga.c:2:20: note: previous definition of 'rcsid' was here
$
まだよわいクソザコナメクジ、オランウータンの餌レベル。
@__SECTIONSTRING() マクロ
しかし現在ではこの「コンパイラの最適化によるメタデータ消滅」を回避するまで改良されています、それは__SECTIONSTRING()マクロ導入で実現されなした(つよい)。
#define __SECTIONSTRING(_sec, _str) \
__asm(".pushsection " #_sec "\n" \
".asciz \"" _str "\"\n" \
".popsection")
#define __IDSTRING(_n,_s) __SECTIONSTRING(.ident,_s)
#define __RCSID(_s) __IDSTRING(rcsid,_s)
さっきも出てきた GNU as の .asciz 疑似命令を使用してインラインアセンブラでオブジェクトの中に文字列を配置してやるようにしたわけです。 これならpass1での最適化で消されることは無いですやね。
そしてさっきのサンプル、ヘッダとCソースで別々に__RCSID()を宣言しても
$ gcc fuga.c
$ ident a.out
a.out:
$NetBSD: crt0.S,v 1.3 2011/07/01 02:59:05 joerg Exp $
$NetBSD: crt0-common.c,v 1.7 2011/06/30 20:07:35 matt Exp $
$NetBSD: crti.S,v 1.1 2010/08/07 18:01:35 joerg Exp $
$NetBSD: crtbegin.S,v 1.2 2010/11/30 18:37:59 joerg Exp $
$NetBSD: fuga.c,v $
$NetBSD: hoge.h,v $
$NetBSD: crtend.S,v 1.1 2010/08/07 18:01:34 joerg Exp $
$NetBSD: crtn.S,v 1.1 2010/08/07 18:01:35 joerg Exp $
コンパイルは成功し、どっちのメタデータも保持できてますな。
@FreeBSD では __RCSID() ではなく __FBSDID() マクロを使う
こちらはFreeBSDで導入された__RCSID()マクロの高機能版です。
#if defined(__GNUC__) || defined(__INTEL_COMPILER)
#define __IDSTRING(name,string) __asm__(".ident\t\"" string "\"")
#else
/*
* The following definition might not work well if used in header files,
* but it should be better than nothing. If you want a "do nothing"
* version, then it should generate some harmless declaration, such as:
* #define __IDSTRING(name,string) struct __hack
*/
#define __IDSTRING(name,string) static const char name[] __unused = string
#endif
/*
* Embed the rcs id of a source file in the resulting library. Note that in
* more recent ELF binutils, we use .ident allowing the ID to be stripped.
* Usage:
* __FBSDID("$FreeBSD$");
*/
#ifndef __FBSDID
#if !defined(lint) && !defined(STRIP_FBSDID)
#define __FBSDID(s) __IDSTRING(__CONCAT(__rcsid_,__LINE__),s)
#else
#define __FBSDID(s) struct __hack
#endif
#endif
やってることは一緒ですが、gccでない環境を考慮して
static const char __rcsid__行番号[] = "$FreeBSD: メタデータ $";
という行番号を変数名に含めるトリックを入れることで、さっきのrcsid重複エラーを回避してるわけです。
正直そこまでやる必要あるんですかね…
@OpenBSDでは
OpenBSDではこの手のバイナリへのメタデータ埋め込みは 止めました。
RCSID()は死ぬべき by theo
こわい(失禁)、ヤクルト宮本の引退試合に集まった PL組事務所なみの恐怖を感じます。
ですので
$ ident /bsd
ident warning: no id keywords in /bsd
ちゅーこと。
openbsd-miscでRCSID削除について議論残ってないかなと思って探したのですが、横浜の先発ピッチャーと同様 見つかりませんでした。
ただしソースコードや設定ファイルそしてスクリプトには継続して残しているので完全否定っていうわけでもないでしょう。
私が思いつく理由についいては
- そもそもバイナリのサイズ的に大いなる無駄である
- OpenBSD は NetBSD が __RCSID() を実装する前にforkしているので、__RCSID()でコンパイル止まる→要らん消せという思想
- 「こんなに知らないとBSD屋さんにはなれない」的なソースの「おまじない」を少しでも減らしたい(つーかtheoは大のマクロ嫌いなのよね…)
- 悪意のあるプログラムが脆弱性のあるバージョンで攻撃に使えるかのチェックにお手軽に使えちゃうじゃん
あたりですかね、あくまで推測ですが。最後のはそれならスクリプトも消すべきなので違うよな…
んでident(1)が使えないことの代償つーか開き直りつーかで、OpenBSDへのバグ報告はリリース配布物で起きたものでないとまず無視されます。 まぁ 元々お約束事が多いので、なかなかバグ報告するのも敷居が高い(釣り用語)のですが。
リリース使うやつは開発者だ、開発版を使う奴は良く訓練された開発者だ、ホントOSSは地獄だぜ!
つーてもコミュニティの人数考えると必要な割り切りですやね。
@突然ですが lint(1) の闇 (リプライズ)
おまけ。
前にlint(1)の説明をした時に書くのを忘れてましたが、lint(1)は以下の事前定義マクロをセットします。
- __LINT__
- lint
- __lint
- __lint__
これらのマクロはあくまでlint(1)の為だけのものなので
__lint__を定義したら通過したが、gccに付属のincludeを使うように直してみよう。
— Ryo ONODERA (@ryo_on) August 25, 2013
とか、コンパイル通すために-D__lint__とか#define __lint__ 1 とか勝手に定義してはいけません。
前も書いた通りlint(1)は
- C99に完全対応してない
- C11への対応は誰やんのよの世界
- そもそもエラーとか警告についてgccとの互換性が低い
という世界なので「lint(1)を騙さなきゃ(義務感)」というチート行為がまかり通ってます。とーぜん騙したコードはチェックを無理矢理通すためだけのもの *3なので正常には動きません。
そういう意味でもlint(1)撲滅してーなと思うんですがねぇ…
@次回予告
*2:ただしここでは RCS でなく SCCS ですな。
*3:SIerいた頃は実装の終わってないとことかバグの残ってるとことか無理矢理「コメントアウトしてテスト通しました進捗100%です」とか言いだすピーばっかで、モックとかスタブそういうチャチなもんじゃねぇ、もっと恐ろしいものの片鱗を味わう日々だったなぁ…