Vous avez évidemment raison que le caoutchouc est pour répondre à la route à certains point. Mais il y a beaucoup de couches à passer avant que vous pouvez trouver! Il semble que vous avez quelques idées préconçues basées sur le DOS jours, et ce n'est pas aussi pertinente.
Il y a eu quelques bons points ici, mais personne ne l'a lié à la précision des diables dans les détails de la source. Donc, afin de vous faire suffisamment désolé que vous avez demandé :) j'ai fait une approfondie trace de l' printf
histoire de GNU libc et Linux..en essayant de ne pas la main-d'onde à propos de l'une de ces étapes. Dans le processus, j'ai apporté quelques-unes de mes connaissances à jour (ATTENTION: Ce n'est pas pour facilement s'ennuyer!):
(Le lien d'origine est http://blog.hostilefork.com/where-printf-rubber-meets-road/, et il sera maintenu. Mais pour éviter lien pourrir ici, c'est le contenu mis en cache.)
Premiers Pas
Nous allons bien sûr de commencer avec le prototype pour le printf, qui est définie dans le fichier libc/libio/stdio.h
extern int printf (__const char *__restrict __format, ...);
Vous ne trouverez pas le code source d'une fonction appelée printf, cependant. Au lieu de cela, dans le fichier /libc/stdio-common/printf.c
vous y trouverez un peu de code associé à une fonction appelée __printf
:
int __printf (const char *format, ...)
{
va_list arg;
int done;
va_start (arg, format);
done = vfprintf (stdout, format, arg);
va_end (arg);
return done;
}
Une macro dans le même fichier met en place une association pour que cette fonction est définie comme un alias pour le non-souligné printf:
ldbl_strong_alias (__printf, printf);
Il est logique que printf serait une fine couche qui appelle vfprintf avec stdout. En effet, la viande de la mise en forme du travail est fait dans vfprintf, que vous trouverez dans libc/stdio-common/vfprintf.c
. C'est assez long, mais vous pouvez voir qu'il est encore dans C!
Plus profondément vers le Bas le Trou de Lapin...
vfprintf mystérieusement appels outchar et outstring, qui sont bizarre les macros définies dans le même fichier:
#define outchar(Ch) \
do \
{ \
register const INT_T outc = (Ch); \
if (PUTC (outc, s) == EOF || done == INT_MAX) \
{ \
done = -1; \
goto all_done; \
} \
++done; \
} \
while (0)
Esquiver la question de savoir pourquoi il est si bizarre, on voit que c'est dépendante de l'énigmatique PUTC, également dans le même fichier:
#define PUTC(C, F) IO_putwc_unlocked (C, F)
Lorsque vous arrivez à la définition de l' IO_putwc_unlocked
en libc/libio/libio.h
, vous pourriez commencer à penser que vous n'avez plus d'attention à la manière de printf œuvres:
#define _IO_putwc_unlocked(_wch, _fp) \
(_IO_BE ((_fp)->_wide_data->_IO_write_ptr \
>= (_fp)->_wide_data->_IO_write_end, 0) \
? __woverflow (_fp, _wch) \
: (_IO_wint_t) (*(_fp)->_wide_data->_IO_write_ptr++ = (_wch)))
Mais en dépit d'être un peu dur à lire, c'est juste faire le tampon de sortie. Si il y a assez de place dans le pointeur de fichier de la mémoire tampon, puis il va juste coller le personnage en lui... mais si pas, il appelle __woverflow
. Comme la seule option lorsque vous n'avez plus de mémoire tampon est de rincer à l'écran (ou quel que soit le périphérique de votre pointeur de fichier représente), on peut espérer trouver l'incantation magique.
Vtables en C?
Si vous avez deviné que nous allons hop à travers un autre frustrant niveau d'indirection, vous auriez raison. Regardez dans la libc/libio/wgenops.c et vous trouverez la définition de la __woverflow
:
wint_t
__woverflow (f, wch)
_IO_FILE *f;
wint_t wch;
{
if (f->_mode == 0)
_IO_fwide (f, 1);
return _IO_OVERFLOW (f, wch);
}
Fondamentalement, les pointeurs de fichiers sont mis en œuvre dans la GNU standard de la bibliothèque en tant qu'objets. Ils ont données des membres, mais aussi les membres de fonction que vous pouvez appeler avec des variations du SAUT de la macro. Dans le fichier libc/libio/libioP.h
vous trouverez un peu de documentation sur cette technique:
/* THE JUMPTABLE FUNCTIONS.
* The _IO_FILE type is used to implement the FILE type in GNU libc,
* as well as the streambuf class in GNU iostreams for C++.
* These are all the same, just used differently.
* An _IO_FILE (or FILE) object is allows followed by a pointer to
* a jump table (of pointers to functions). The pointer is accessed
* with the _IO_JUMPS macro. The jump table has a eccentric format,
* so as to be compatible with the layout of a C++ virtual function table.
* (as implemented by g++). When a pointer to a streambuf object is
* coerced to an (_IO_FILE*), then _IO_JUMPS on the result just
* happens to point to the virtual function table of the streambuf.
* Thus the _IO_JUMPS function table used for C stdio/libio does
* double duty as the virtual function table for C++ streambuf.
*
* The entries in the _IO_JUMPS function table (and hence also the
* virtual functions of a streambuf) are described below.
* The first parameter of each function entry is the _IO_FILE/streambuf
* object being acted on (i.e. the 'this' parameter).
*/
Ainsi, lorsque nous trouvons IO_OVERFLOW
en libc/libio/genops.c
, nous trouver, c'est une macro qui appelle un "1-parameter" __overflow
méthode sur le pointeur de fichier:
#define IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
Le saut de tables pour les différents pointeur de fichier types sont dans la libc/libio/fileops.c
const struct _IO_jump_t _IO_file_jumps =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, INTUSE(_IO_file_finish)),
JUMP_INIT(overflow, INTUSE(_IO_file_overflow)),
JUMP_INIT(underflow, INTUSE(_IO_file_underflow)),
JUMP_INIT(uflow, INTUSE(_IO_default_uflow)),
JUMP_INIT(pbackfail, INTUSE(_IO_default_pbackfail)),
JUMP_INIT(xsputn, INTUSE(_IO_file_xsputn)),
JUMP_INIT(xsgetn, INTUSE(_IO_file_xsgetn)),
JUMP_INIT(seekoff, _IO_new_file_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_new_file_setbuf),
JUMP_INIT(sync, _IO_new_file_sync),
JUMP_INIT(doallocate, INTUSE(_IO_file_doallocate)),
JUMP_INIT(read, INTUSE(_IO_file_read)),
JUMP_INIT(write, _IO_new_file_write),
JUMP_INIT(seek, INTUSE(_IO_file_seek)),
JUMP_INIT(close, INTUSE(_IO_file_close)),
JUMP_INIT(stat, INTUSE(_IO_file_stat)),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
libc_hidden_data_def (_IO_file_jumps)
Il y a également un #define, ce qui équivaut_IO_new_file_overflow
avec _IO_file_overflow
, et le premier est défini dans le même fichier source. (Note: INTUSE est juste une macro qui marque les fonctions qui sont à usage interne, cela ne veut pas dire quelque chose comme "cette fonction utilise une interruption")
Sommes-nous encore là?!
Le code source pour _IO_new_file_overflow un groupe de plus de la mémoire tampon de la manipulation, mais il n'appelez _IO_do_flush
:
#define _IO_do_flush(_f) \
INTUSE(_IO_do_write)(_f, (_f)->_IO_write_base, \
(_f)->_IO_write_ptr-(_f)->_IO_write_base)
Nous sommes maintenant à un point où _IO_do_write est probablement là où le caoutchouc répond effectivement à la route: un tampon, réels, directs écrire à un périphérique d'e/S. Au moins on peut l'espérer! Elle est représentée par une macro pour _IO_new_do_write et nous avons ceci:
static
_IO_size_t
new_do_write (fp, data, to_do)
_IO_FILE *fp;
const char *data;
_IO_size_t to_do;
{
_IO_size_t count;
if (fp->_flags & _IO_IS_APPENDING)
/* On a system without a proper O_APPEND implementation,
you would need to sys_seek(0, SEEK_END) here, but is
is not needed nor desirable for Unix- or Posix-like systems.
Instead, just indicate that offset (before and after) is
unpredictable. */
fp->_offset = _IO_pos_BAD;
else if (fp->_IO_read_end != fp->_IO_write_base)
{
_IO_off64_t new_pos
= _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
if (new_pos == _IO_pos_BAD)
return 0;
fp->_offset = new_pos;
}
count = _IO_SYSWRITE (fp, data, to_do);
if (fp->_cur_column && count)
fp->_cur_column = INTUSE(_IO_adjust_column) (fp->_cur_column - 1, data,
count) + 1;
_IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
fp->_IO_write_end = (fp->_mode <= 0
&& (fp->_flags & (_IO_LINE_BUF+_IO_UNBUFFERED))
? fp->_IO_buf_base : fp->_IO_buf_end);
return count;
}
Malheureusement nous nous sommes coincés à nouveau... _IO_SYSWRITE
est en train de faire le travail:
/* The 'syswrite' hook is used to write data from an existing buffer
to an external file. It generalizes the Unix write(2) function.
It matches the streambuf::sys_write virtual function, which is
specific to this implementation. */
typedef _IO_ssize_t (*_IO_write_t) (_IO_FILE *, const void *, _IO_ssize_t);
#define _IO_SYSWRITE(FP, DATA, LEN) JUMP2 (__write, FP, DATA, LEN)
#define _IO_WSYSWRITE(FP, DATA, LEN) WJUMP2 (__write, FP, DATA, LEN)
Donc, à l'intérieur de la do_write nous appelons la méthode d'écriture sur le pointeur de fichier. Nous savons de par notre saut tableau ci-dessus qui est mappé à _IO_new_file_write, alors à quoi ça sert?
_IO_ssize_t
_IO_new_file_write (f, data, n)
_IO_FILE *f;
const void *data;
_IO_ssize_t n;
{
_IO_ssize_t to_do = n;
while (to_do > 0)
{
_IO_ssize_t count = (__builtin_expect (f->_flags2
& _IO_FLAGS2_NOTCANCEL, 0)
? write_not_cancel (f->_fileno, data, to_do)
: write (f->_fileno, data, to_do));
if (count < 0)
{
f->_flags |= _IO_ERR_SEEN;
break;
}
to_do -= count;
data = (void *) ((char *) data + count);
}
n -= to_do;
if (f->_offset >= 0)
f->_offset += n;
return n;
}
Maintenant, il appelle à écrire! Bien où est la mise en œuvre pour cela? Vous trouverez écrire en libc/posix/unistd.h
:
/* Write N bytes of BUF to FD. Return the number written, or -1.
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t write (int __fd, __const void *__buf, size_t __n) __wur;
(Remarque: __wur
est une macro pour __attribute__ ((__warn_unused_result__)))
Des fonctions Générées à Partir d'une Table
C'est seulement un prototype pour l'écriture. Vous ne trouverez pas d'écrire.fichier c pour Linux dans la GNU de la bibliothèque standard. Au lieu de cela, vous trouverez une plate-forme spécifique méthodes de connexion à l'OS la fonction d'écriture dans diverses manières, le tout dans la libc/sysdeps/ répertoire.
Nous allons continuer à suivre ainsi que la façon dont Linux fait. Il y a un fichier appelé sysdeps/unix/syscalls.list
qui est utilisé pour générer la fonction d'écriture automatique. Les données pertinentes à partir de la table est la suivante:
File name: write
Caller: "-" (i.e. Not Applicable)
Syscall name: write
Args: Ci:ibn
Strong name: __libc_write
Weak names: __write, write
Pas du tout mystérieux, à l'exception de l' Ci:ibn
. Le C signifie "annulable". Le côlon sépare le type de retour de l'argument, les types, et si vous voulez une explication plus approfondie de ce qu'ils signifient, alors vous pouvez voir le commentaire dans le script shell qui génère le code, libc/sysdeps/unix/make-syscalls.sh
.
Alors maintenant, nous nous attendons à être en mesure de relier à l'encontre d'une fonction appelée __libc_l'écriture qui est généré par ce script shell. Mais qu'en est-il généré? Code C qui implémente l'écriture via une macro appelée SYS_ify, que vous trouverez dans sysdeps/unix/sysdep.h
#define SYS_ify(syscall_name) __NR_##syscall_name
Ah, le bon vieux jeton-coller :P. Donc, fondamentalement, la mise en œuvre de cette __libc_write
devient rien de plus qu'un proxy invocation de la syscall fonction avec un paramètre nommé __NR_write
, et les autres arguments.
Là Où Le Trottoir Se Termine...
Je sais que cela a été un voyage fascinant, mais maintenant nous sommes à la fin de la GNU libc. Ce nombre __NR_write
est défini par Linux. Pour 32-bit X86 architectures, il vous mènera à l' linux/arch/x86/include/asm/unistd_32.h
:
#define __NR_write 4
La seule chose qui reste à regarder, puis, est la mise en œuvre de syscall. Que je peut faire à un certain point, mais pour l'instant je vais juste le point vous sur certaines références pour comment ajouter un appel système pour Linux.