puts()を読み始めるためにはまずputs()がどこで宣言されているか見つけなくてはならない。そのための方法をいくつか紹介する。
読み始める前にでインストールしたデバッグシンボルを用いてソースがどこにあるのかを特定する。まず、以下のようなプログラムを書きコンパイルをしよう。ここではプログラム本体のデバッグを目的とはしていないので-gオプションを付ける必要はない。
Sample Program
#include <stdio.h>
int main(){
puts("Hello!!");
}Sample gdb commands
tui enable
b puts
rそうすると、puts()にはいったところでプログラムが止まるのでそのファイル名を確認してソースコードに対してfind ./ -name filename.cなどで検索をかけたり、そのままGDBで読み進めれば良いと思う。
デバッガを使う利点は関数がシステムや設定によって宣言場所が違う場合に、自分の環境ではどれが使われているのかがすぐに分かるという点である。また、デバッグシンボルが使えるものならばすべてのソフトウェアで対応できる手法なので最初はこれで見つけ出すのが一番早いであろう。
難点としてはデバッグシンボルがない、または何らかの理由でデバッグすることができない場合は使えないというところであろう。しかし、今現在UbuntuとFedoraではパッケージマネージャから多くのデバッグシンボルをインストールできるので、その範囲内のソフトウェアなら特に問題とはならない。
各ナビゲーションシステムにあるシンボル検索や関数検索から検索する。ここではwoboqとSourceWebで動作を確認したがどちらも正しいところを示している。 こちらの利点はソースがあれば問題なくできるところ、いちいちソフトウェアを起動する必要がないのでMySQLやLinuxといった複雑なソフトウェアでも問題なく読めることであろう。欠点はそこまで結果に対して信用できないことである。一応WoboqやSourceWebやDXRはC/C++の場合はコンパイラを通しているらしいがgccではないので一部のgcc拡張には対応できないし、いくつかシンボルを検知できていないものも散見される。最終的にはやはりgrepを使うことになる。
これはある程度ソースコードを読み慣れないとできないことだが、慣れればこれが一番良いと思われる。なぜなら、今回は標準ライブラリを読むという点からたまたま読みたい関数を指定することができたが一般にソフトウェアのリーディングをする場合はどの関数を読めばいいかなんてものは知ることができないからである。せいぜい特定の機能に関する部分というところまでしか絞ることができない。その場合は当たりをつけるということが非常に重要になる。ここではそのあたりの付け方について記す。
まず、glibcのトップのファイル構造を見てみよう。以下のリンクか自身がダウンロードしたディレクトリでlsを実行してみよう。以下のような結果になる。
https://code.woboq.org/userspace/glibc/
ls glibc
COPYING NEWS config.make.in extra-lib.mk inet malloc po socket test-skeleton.c
COPYING.LIB README configure gen-locales.mk intl manual posix soft-fp time
ChangeLog Rules configure.ac gmon io math pwd stdio-common timezone
ChangeLog.old abi-tags conform gnulib libc-abis mathvec resolv stdlib version.h
INSTALL aclocal.m4 crypt grp libidn misc resource streams wcsmbs
LICENSES argp csu gshadow libio nis rt string wctype
MAINTAINERS assert ctype hesiod libof-iterator.mk nptl scripts sunrpc
Makeconfig benchtests debug hurd locale nptl_db setjmp support
Makefile bits dirent iconv localedata nscd shadow sysdeps
Makefile.in catgets dlfcn iconvdata login nss shlib-versions sysvipc
Makerules config.h.in elf include mach o-iterator.mk signal termios
ここで注目してほしいのがファイルやディレクトリの名前である。見て分かる通りこれらの名前は変数名等と同じように意味を持つものになっている。 例えば"assert"はC言語の標準ヘッダである"assert.h"関連の関数があるであろうと予測できるし、"time"も同様"time.h"関連であろうということが予測できる。 また、わからないものは調べれば大抵はすぐに出てくる。例えば"elf"や"hurd"などは有名なのでこれらは問題なく出てくる。 これで大まかにどこに何があるのかの見当がついた。
それではここでputs()がどこにあるのかを予想してみよう。まず、puts()がおいてあるヘッダは"stdio.h"である。この名前から怪しそうなのは"stdio-common"と"libio"と"io"であろう。だが、現時点ではこれらの明確な差はわかっていないのでとりあえずそれぞれ何がはいっているのかls io libio stdio-commonで見てみよう。
ls io libio stdio-common
io:
Makefile fchdir.c ftwtest-sh lxstat64.c posix_fallocate64.c tst-copy_file_range-compat.c tst-renameat.c
Versions fchmod.c ftwtest.c mkdir.c ppoll.c tst-copy_file_range.c tst-statvfs.c
access.c fchmodat.c futimens.c mkdirat.c pwd.c tst-faccessat.c tst-symlinkat.c
bits fchown.c fxstat.c mkfifo.c read.c tst-fchmodat.c tst-ttyname_r.c
bug-ftw1.c fchownat.c fxstat64.c mkfifoat.c readlink.c tst-fchownat.c tst-unlinkat.c
bug-ftw2.c fcntl.c fxstatat.c mknod.c readlinkat.c tst-fcntl.c ttyname.c
bug-ftw3.c fcntl.h fxstatat64.c mknodat.c rmdir.c tst-fstatat.c ttyname_r.c
bug-ftw4.c flock.c getcwd.c open.c sendfile.c tst-fts-lfs.c umask.c
bug-ftw5.c fstat.c getdirname.c open64.c sendfile64.c tst-fts.c unlink.c
chdir.c fstat64.c getwd.c open64_2.c stat.c tst-futimesat.c unlinkat.c
chmod.c fstatat.c isatty.c open_2.c stat64.c tst-getcwd-abspath.c utime.c
chown.c fstatat64.c lchmod.c openat.c statfs.c tst-getcwd.c utime.h
close.c fstatfs.c lchown.c openat64.c statfs64.c tst-linkat.c utimensat.c
copy_file_range-compat.c fstatfs64.c link.c openat64_2.c statvfs.c tst-mkdirat.c write.c
copy_file_range.c fstatvfs.c linkat.c openat_2.c statvfs64.c tst-mkfifoat.c xmknod.c
creat.c fstatvfs64.c lockf.c pipe.c symlink.c tst-mknodat.c xmknodat.c
creat64.c fts.c lockf64.c pipe2.c symlinkat.c tst-open-tmpfile.c xstat.c
dup.c fts.h lseek.c poll.c sys tst-openat.c xstat64.c
dup2.c fts64.c lseek64.c poll.h test-lfs.c tst-posix_fallocate-common.c
dup3.c ftw.c lstat.c posix_fadvise.c test-stat.c tst-posix_fallocate.c
euidaccess.c ftw.h lstat64.c posix_fadvise64.c test-stat2.c tst-posix_fallocate64.c
faccessat.c ftw64.c lxstat.c posix_fallocate.c test-utime.c tst-readlinkat.c
libio:
Depend clearerr_u.c getwc_u.c iogets.c oldiofsetpos64.c tst-eof.c tst-widetext.c
Makefile fcloseall.c getwchar.c iogetwline.c oldiopopen.c tst-ext.c tst-widetext.input
Versions feof.c getwchar_u.c iolibio.h oldpclose.c tst-ext2.c tst-wmemstream1.c
__fbufsize.c feof_u.c iofclose.c iopadn.c oldstdfiles.c tst-fgetc-after-eof.c tst-wmemstream2.c
__flbf.c ferror.c iofdopen.c iopopen.c oldtmpfile.c tst-fgetwc.c tst-wmemstream3.c
__fpending.c ferror_u.c iofflush.c ioputs.c pclose.c tst-fgetwc.input tst_getwc.c
__fpurge.c filedoalloc.c iofflush_u.c ioseekoff.c peekc.c tst-fgetws.c tst_getwc.input
__freadable.c fileno.c iofgetpos.c ioseekpos.c putc.c tst-fopenloc.c tst_putwc.c
__freading.c fileops.c iofgetpos64.c iosetbuffer.c putc_u.c tst-fopenloc2.c tst_swprintf.c
__fsetlocking.c fmemopen.c iofgets.c iosetvbuf.c putchar.c tst-fputws.c tst_swscanf.c
__fwritable.c fputc.c iofgets_u.c ioungetc.c putchar_u.c tst-freopen.c tst_wprintf.c
__fwriting.c fputc_u.c iofgetws.c ioungetwc.c putwc.c tst-fseek.c tst_wprintf2.c
bits fputwc.c iofgetws_u.c iovdprintf.c putwc_u.c tst-ftell-active-handler.c tst_wscanf.c
bug-fopena+.c fputwc_u.c iofopen.c iovsprintf.c putwchar.c tst-ftell-append.c tst_wscanf.input
bug-fseek.c freopen.c iofopen64.c iovsscanf.c putwchar_u.c tst-ftell-partial-wide.c vasprintf.c
bug-ftell.c freopen64.c iofopncook.c iovswscanf.c rewind.c tst-fwrite-error.c vscanf.c
bug-memstream1.c fseek.c iofputs.c iowpadn.c setbuf.c tst-memstream1.c vsnprintf.c
bug-mmap-fflush.c fseeko.c iofputs_u.c libc_fatal.c setlinebuf.c tst-memstream2.c vswprintf.c
bug-rewind.c fseeko64.c iofputws.c libio.h stdfiles.c tst-memstream3.c vtables.c
bug-rewind2.c ftello.c iofputws_u.c libioP.h stdio.c tst-mmap-eofsync.c vwprintf.c
bug-ungetc.c ftello64.c iofread.c memstream.c stdio.h tst-mmap-fflushsync.c vwscanf.c
bug-ungetc2.c fwide.c iofread_u.c obprintf.c strfile.h tst-mmap-offend.c wfiledoalloc.c
bug-ungetc3.c fwprintf.c iofsetpos.c oldfileops.c strops.c tst-mmap-setvbuf.c wfileops.c
bug-ungetc4.c fwscanf.c iofsetpos64.c oldfmemopen.c swprintf.c tst-mmap2-eofsync.c wgenops.c
bug-ungetwc1.c genops.c ioftell.c oldiofclose.c swscanf.c tst-popen1.c wmemstream.c
bug-ungetwc2.c getc.c iofwide.c oldiofdopen.c test-fmemopen.c tst-setvbuf1.c wprintf.c
bug-wfflush.c getc_u.c iofwrite.c oldiofgetpos.c test-freopen.c tst-sscanf.c wscanf.c
bug-wmemstream1.c getchar.c iofwrite_u.c oldiofgetpos64.c test-freopen.sh tst-swscanf.c wstrops.c
bug-wsetpos.c getchar_u.c iogetdelim.c oldiofopen.c tst-atime.c tst-ungetwc1.c
clearerr.c getwc.c iogetline.c oldiofsetpos.c tst-bz22415.c tst-ungetwc2.c
stdio-common:
Depend bug22.c fxprintf.c psignal.c siglist.c tst-fileno.c tst-sprintf.c
Makefile bug23-2.c gentempfd.c putw.c snprintf.c tst-fmemopen.c tst-sprintf2.c
Versions bug23-3.c getline.c reg-modifier.c sprintf.c tst-fmemopen2.c tst-sprintf3.c
_i18n_number.h bug23-4.c getw.c reg-printf.c sscanf.c tst-fmemopen3.c tst-sscanf.c
_itoa.c bug23.c isoc99_fscanf.c reg-type.c stdio_ext.h tst-fmemopen4.c tst-swprintf.c
_itowa.c bug24.c isoc99_scanf.c remove.c stdio_lim.h.in tst-fphex-wide.c tst-swscanf.c
_itowa.h bug25.c isoc99_sscanf.c rename.c tempnam.c tst-fphex.c tst-tmpnam.c
asprintf.c bug26.c isoc99_vfscanf.c renameat.c tempname.c tst-fseek.c tst-unbputc.c
bits bug3.c isoc99_vscanf.c scanf.c temptest.c tst-fwrite.c tst-unbputc.sh
bug-vfprintf-nargs.c bug4.c isoc99_vsscanf.c scanf1.c test-fseek.c tst-gets.c tst-ungetc.c
bug1.c bug5.c itoa-digits.c scanf10.c test-fwrite.c tst-gets.input tst-unlockedio.c
bug1.input bug6.c itoa-udigits.c scanf11.c test-popen.c tst-grouping.c tst-vfprintf-mbs-prec.c
bug10.c bug6.input itowa-digits.c scanf12.c test-vfprintf.c tst-long-dbl-fphex.c tst-vfprintf-user-type.c
bug11.c bug7.c perror.c scanf12.input test_rdwr.c tst-obprintf.c tst-vfprintf-width-prec.c
bug12.c bug8.c printf-parse.h scanf13.c tfformat.c tst-perror.c tst-wc-printf.c
bug13.c bug9.c printf-parsemb.c scanf14.c tiformat.c tst-popen.c tstdiomisc.c
bug14.c ctermid.c printf-parsewc.c scanf15.c tllformat.c tst-popen2.c tstgetln.c
bug16.c cuserid.c printf-prs.c scanf16.c tmpfile.c tst-printf-bz18872.sh tstgetln.input
bug17.c dprintf.c printf.c scanf17.c tmpfile64.c tst-printf-round.c tstscanf.c
bug18.c errlist.c printf.h scanf2.c tmpnam.c tst-printf.c tstscanf.input
bug18a.c errnobug.c printf_fp.c scanf3.c tmpnam_r.c tst-printf.sh vfprintf.c
bug19.c flockfile.c printf_fphex.c scanf4.c tst-cookie.c tst-printfsz.c vfscanf.c
bug19a.c fprintf.c printf_size.c scanf5.c tst-fdopen.c tst-put-error.c vfwprintf.c
bug2.c fscanf.c psiginfo-data.h scanf7.c tst-ferror.c tst-rndseek.c vfwscanf.c
bug20.c ftrylockfile.c psiginfo-define.h scanf8.c tst-ferror.input tst-setvbuf1.c vprintf.c
bug21.c funlockfile.c psiginfo.c scanf9.c tst-fgets.c tst-setvbuf1.expect xbug.c
非常に多くのファイルが有って少し驚くかもしれないが大したことはない。よく見てみると標準関数の名前が並んでいることがわかる。test-*やtst-*はおそらくテストケースが入っているファイルであろう。ここまで来たらどこにあるかはすぐに分かると思われる。そう、libio/ioputs.cである。
このようによく管理されているプロジェクトは楽に目的の部分にたどり着くことができるようにちゃんと作られているのだ。他のプロジェクトの場合も同様である。はじめは見てすぐにこのようなことを理解することは難しいかもしれないがめげずに経験を積むようにしてほしい。
ようやくここでputs()の関数を読むことにしよう。各自ナビゲーションシステムやデバッガでputs()を表示してほしいが一応ioputs.cの全文をここにも貼ることにする。
glibc/libio/ioputs.c
/* Copyright (C) 1993-2018 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>.
As a special exception, if you link the code in this file with
files compiled with a GNU compiler to produce an executable,
that does not cause the resulting executable to be covered by
the GNU Lesser General Public License. This exception does not
however invalidate any other reasons why the executable file
might be covered by the GNU Lesser General Public License.
This exception applies to code released by its copyright holders
in files containing the exception. */
#include "libioP.h"
#include <string.h>
#include <limits.h>
int
_IO_puts (const char *str)
{
int result = EOF;
size_t len = strlen (str);
_IO_acquire_lock (_IO_stdout);
if ((_IO_vtable_offset (_IO_stdout) != 0
|| _IO_fwide (_IO_stdout, -1) == -1)
&& _IO_sputn (_IO_stdout, str, len) == len
&& _IO_putc_unlocked ('\n', _IO_stdout) != EOF)
result = MIN (INT_MAX, len + 1);
_IO_release_lock (_IO_stdout);
return result;
}
weak_alias (_IO_puts, puts)まずはじめに気になるのがweak_alias (_IO_puts, puts)であろう。そこでweak_aliasの定義を確認してみよう。
glibc/include/libc-symbols.h
# define weak_alias(name, aliasname) _weak_alias (name, aliasname)
# define _weak_alias(name, aliasname) \
extern __typeof (name) aliasname __attribute__ ((weak, alias (#name)));いきなり訳がわからないとは思うのでここは少しわかりやすく分け目を大きくしてみようと思う。
glibc/include/libc-symbols.h modified
# define weak_alias(name, aliasname) _weak_alias (name, aliasname)
# define _weak_alias(name, aliasname) \
extern __typeof (name) aliasname __attribute__ ((weak, alias (#name)));これで少しはわかりやすくなったかと思う。
ひとつずつ見ていこう、まずexternは外部に宣言がされているという意味の修飾子だ。これは、関数の場合は意味をなさないが変数の場合は存在のみを定義して実態を作らないという意味を持つ。が、ここでは aliasname は puts 、即ち関数なのでとりあえず無視しよう。
次に__typeof (name)である。これは typeof 演算子であろうというのは簡単に予測できる。
最後が __attribute__ ((weak, alias (#name))); である。これは"alias gcc extension"等で検索すれば出てくるgccの拡張機能である alias だ。簡単に言えば別名というわけだ。weakとあるのは同ページの下の方に記述があるが別で宣言があったら上書きできるという意味だ。つまりputs()という名前の別の関数をプログラムで定義しても問題なくコンパイルされ、かつputs()の呼び出し時はその新しく書いた方が使われるようになるわけだ。C言語では普通名前が衝突するとエラーが出るので普通の挙動ではないことがわかるだろう。#name の部分はその部分をそのまま文字列、即ち "name" にするという意味だ。こうする理由は上のリンクをよく見ればわかるが__attribute__に使う引数が文字列である必要があるからだ。
結果としてweak_alias(_IO_puts,puts)は以下のように展開される。
extern __typeof (_IO_puts) puts __attribute__ ((weak, alias ("_IO_puts")));こうして、putsは_IO_putsのaliasであると定義されるわけだ。
putsの謎が解けたところでその中身を読んでいこう。
glibc/libio/ioputs.c
int
_IO_puts (const char *str)
{
int result = EOF;
size_t len = strlen (str);
_IO_acquire_lock (_IO_stdout); // 1
if ((_IO_vtable_offset (_IO_stdout) != 0 // 2
|| _IO_fwide (_IO_stdout, -1) == -1) // 3
&& _IO_sputn (_IO_stdout, str, len) == len // 4
&& _IO_putc_unlocked ('\n', _IO_stdout) != EOF) // 4
result = MIN (INT_MAX, len + 1);
_IO_release_lock (_IO_stdout); // 1
return result;
}読みやすさのために番号を振った。
一番最初につまずくのは1番の _IO_acquire_lock _IO_release_lock だと思う。これはマルチスレッドプログラミングで使われるlock freeと呼ばれるものだ。かいつまんで話すとマルチスレッドプログラミングをするときに同じリソースに対して違うスレッドが同時に使うという状況が起こることがある。それを単純に許してしまうと同時に別の値が上書きされたりして、結果挙動がおかしくなることがある。それを防ぐためにあるスレッドが使っている限りは別のスレッドからは使えないように鍵をかけ、使い終わったらそれを開放する、という意味でロックフリーというわけだ。この部分はこれ以上深くは追わないこととする。理由はwoboq等でマクロの展開結果を見ればわかるがとんでもない量のコードになっているからだ。ここはputs()の核心ではないのであまり時間をかけないためにとばすことにする。
次に2番の部分だが、結論からするとこの部分では何もしない、何もおこらないことに普通はなっている。 どういうことか定義部分を見てみよう。
glibc/libio/libioP.h
/* Setting this macro to 1 enables the use of the _vtable_offset bias
in _IO_JUMPS_FUNCS, below. This is only needed for new-format
_IO_FILE in libc that must support old binaries (see oldfileops.c). */
#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1) && !defined _IO_USE_OLD_IO_FILE
# define _IO_JUMPS_OFFSET 1
#else
# define _IO_JUMPS_OFFSET 0
#endif
// CUT
#if _IO_JUMPS_OFFSET
# define _IO_JUMPS_FUNC(THIS) \
(IO_validate_vtable \
(*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS) \
+ (THIS)->_vtable_offset)))
# define _IO_vtable_offset(THIS) (THIS)->_vtable_offset
#else
# define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS_FILE_plus (THIS)))
# define _IO_vtable_offset(THIS) 0
#endif注目してほしいのが _IO_JUMPS_OFFSET の定義をしている上の部分のコメントである。単純に言えばold binary用だから今は使ってないということだ。
ここでは具体的な言及は避けるが、実行ファイルにも新旧や種類や変更があっておそらくこれは昔の実行ファイルを使う場合に必要になるものなのだと推測できる。
実際GDBでステップ実行をすると_IO_vtable_offsetの部分はコンパイル時に評価がfalseであると予め計算されるため実行ファイル上ではなかったもの扱いになっている。
次に3番だが、fwideについてはman pageが存在しておりおそらく同じ関数だと予測できる。ソースを確認してもよいがソースを読むときに重要なのは必要のないところは読まないことなのでここではこれ以上の言及はしない。この部分は要はstdoutをバイトストリームに変更するだけである。
4番についてだが、デバッガで確認すればわかるがここが出力を行っている関数である。そのため、まずここを読む前に_IO_stdoutの型を調べようと思う。ソースを読む上ではロジックを読むのも重要であるがそれ以上に型、特にstruct等ユーザーが定義した型の情報が大事になる。なぜなら、処理というのは型の意味する情報があって初めて作られるからだ。では、早速見てみよう。
glibc/libio/bits/types/struct_FILE.h
struct _IO_FILE
{
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
/* The following pointers correspond to the C++ streambuf protocol. */
char *_IO_read_ptr; /* Current read pointer */
char *_IO_read_end; /* End of get area. */
char *_IO_read_base; /* Start of putback+get area. */
char *_IO_write_base; /* Start of put area. */
char *_IO_write_ptr; /* Current put pointer. */
char *_IO_write_end; /* End of put area. */
char *_IO_buf_base; /* Start of reserve area. */
char *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
int _flags2;
__off_t _old_offset; /* This used to be _offset but it's too small. */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
struct _IO_FILE_complete
{
struct _IO_FILE _file;
#endif
__off64_t _offset;
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
size_t __pad5;
int _mode;
/* Make sure we don't get into trouble again. */
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};これまた長くて大変だが、すべてを理解する必要はない。とりあえずは名前から推測してみよう。その前にあまり知らないであろう単語の説明をする。
- offset
直訳すると「埋め合わせ・差引」だが、とりわけコンピュータ系では配列や構造体で始めから何バイト後ろにあるか、という意味で使われることが多い。
- vtable
省略された部分を書くと"virtual table"で、コンピュータ系では関数ポインタの列(構造体)という意味になる。
- list
アルゴリズムで使われる言葉で次のデータを指し示すポインタによって連結されたデータ構造のことである。chainもおそらく同じ意味で使われていると思われる。
- buf
今回の場合はおそらくlistと対照して普通のC言語の配列を意味するものだと思われる。
で、このデータ構造で気になるのが_IO_USE_OLD_IO_FILEだが基本OLDと書かれているものは互換性のために残ってるだけで今のPCで使われることはないと推測して良い。なのでこの部分はないものとして考える。ほかはとりあえずは今はコメントと単語から推測できる程度がわかっていれば問題ない。
では話を戻して4番を見ていこう。まず下のソースを見てほしい、これは4の関数っぽいものの宣言部分とそれに関連したところだ。
glibc/libio/libioP.h
#define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS)
#define JUMP_FIELD(TYPE, NAME) TYPE NAME
#define JUMP0(FUNC, THIS) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS)
#define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)
#define JUMP3(FUNC, THIS, X1,X2,X3) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1,X2, X3)
#define JUMP_INIT(NAME, VALUE) VALUE
#define JUMP_INIT_DUMMY JUMP_INIT(dummy, 0), JUMP_INIT (dummy2, 0)
//CUT
/* The 'xsputn' hook writes upto N characters from buffer DATA.
Returns EOF or the number of character actually written.
It matches the streambuf::xsputn virtual function. */
typedef size_t (*_IO_xsputn_t) (FILE *FP, const void *DATA,
size_t N);
#define _IO_XSPUTN(FP, DATA, N) JUMP2 (__xsputn, FP, DATA, N)
//CUT
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};
/* We always allocate an extra word following an _IO_FILE.
This contains a pointer to the function jump table used.
This is for compatibility with C++ streambuf; the word can
be used to smash to a pointer to a virtual function table. */
struct _IO_FILE_plus
{
FILE file;
const struct _IO_jump_t *vtable;
};
//CUT
#define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n)ここのあたりは少し技巧的である。何がしたくてこんな長ったらしくなっているかというとC++のクラスと同じことを実現しようとしているのだ。
ここで少しC++のクラスの内部の挙動について解説すると、ほぼ上のコードと同じなのだが各クラスごとにベクターテーブルと呼ばれるメソッドのポインターをまとめた物がある。このベクターテーブルは継承関係にあるものは同じオフセットに同型同名(オーバーライドされていることもあるので同じメソッドとは限らない)のメソッドが存在して、これによってオーバーライドを実現している。 また、メソッドの呼び出し時にはベクターテーブルを参照して呼び出すわけだがメソッドの引数には隠し引数としてオブジェクトへのポインタが渡されている。このポインタを使ってオブジェクトのフィールドにアクセスするわけだ。
上のコードはつまりそれと同じ挙動をするプログラムを作成している。まずFILE構造体を拡張する形で_IO_FILE_plus構造体を作成している。stdin等をじっくり読んでいる人はすでに知っていると思うがstdin,out,errはすべて_IO_FILE_plus構造体で宣言されている。以下がその部分である。
glibc/libio/stdfiles.c
#ifdef _IO_MTSAFE_IO
# define DEF_STDFILE(NAME, FD, CHAIN, FLAGS) \
static _IO_lock_t _IO_stdfile_##FD##_lock = _IO_lock_initializer; \
static struct _IO_wide_data _IO_wide_data_##FD \
= { ._wide_vtable = &_IO_wfile_jumps }; \
struct _IO_FILE_plus NAME \
= {FILEBUF_LITERAL(CHAIN, FLAGS, FD, &_IO_wide_data_##FD), \
&_IO_file_jumps};
#else
# define DEF_STDFILE(NAME, FD, CHAIN, FLAGS) \
static struct _IO_wide_data _IO_wide_data_##FD \
= { ._wide_vtable = &_IO_wfile_jumps }; \
struct _IO_FILE_plus NAME \
= {FILEBUF_LITERAL(CHAIN, FLAGS, FD, &_IO_wide_data_##FD), \
&_IO_file_jumps};
#endif
DEF_STDFILE(_IO_2_1_stdin_, 0, 0, _IO_NO_WRITES);
DEF_STDFILE(_IO_2_1_stdout_, 1, &_IO_2_1_stdin_, _IO_NO_READS);
DEF_STDFILE(_IO_2_1_stderr_, 2, &_IO_2_1_stdout_, _IO_NO_READS+_IO_UNBUFFERED);非常にややこしいがこの部分は今は特別読み込む必要はないのでwoboqやgdbで展開結果だけを表示させれば良いと思う。とりあえず_IO_FILE_plus構造体で宣言されていることを知っていることが重要である。
そしてもう一つ重要なのが、structの各部分に何が代入されているかである。今回はとりわけvtablesに何が入っているかであろう。上の展開結果をwoboq等で調べれば分かるが_IO_file_jumpsが代入されていることが分かる。