SORU
14 Mart 2010, Pazar


C fonksiyon tanımları C/montaj olmadan

Ben printf() gibi fonksiyonları, son adım, satır içi derleme kullanılarak tanımlanan düşünmüşümdür. Derin istasyonu. derinliklerinde.h aslında yapmak için CPU söyleyen bazı sembolik makina kodu gömülü. Dos, örneğin, ilk movıng bellek dize başında yer veya ve intterupt aramaktan daha kaydı uygulanmıştır hatırlıyorum.

Visual Studio x 64 sürümü, satır içi çevirici hiç destek yok beri ancak, beni çevirici tanımlı işlevler olabilir ne kadar hiç C/C merak ediyorum . Nasıl printf() gibi bir kütüphane işlevi çevirici kod kullanmadan C/C yürütülmesi mu? Aslında doğru yazılım kesmesi ne yürütür? Teşekkürler.

CEVAP
15 Mart 2010, PAZARTESİ


Kauçuk yol karşılamak için elbette haklısın . ben^>bazınokta. Ama bir şey varçokbulmak için önce geçmesi için katmanları orası! Bazı önyargılar DOS günlerine göre var gibi geliyor, ve o da artık anlamlı değil.

Bazı iyi bir genel puan burada yapılmış, ancak hiçbir kaynak detaylarda hassas şeytanlar bağlı. Yani sen istediğin kadar pişman etmek için:) printf hikayenin kapsamlı bir izleme yaptım . ben^>GNU C kütüphanesi ve Linux için... el-dalga adımları herhangi bir konuda çalışıyorum. Ben getirdim süreci kendi bilgilerimi güncel (UYARI: kolay bıkmak için değil!):

(Orijinal bağlantı http://blog.hostilefork.com/where-printf-rubber-meets-road/ ve orada muhafaza edilecek. Ama link burada çürüyen önlemek için içerik önbelleğe alınır.)

İlk Adımları

Elbette dosya libc/libio/stdio.h tanımlanmış olan, basit bir beşgen kullanmaktır prototipi ile başlayacağız

extern int printf (__const char *__restrict __format, ...);

Bir işlev, basit bir beşgen kullanmaktır denir için kaynak kodu, ancak bulamazsınız. Bunun yerine, dosya /libc/stdio-common/printf.c kod biraz fonksiyonu __printf adı ile ilişkili

int __printf (const char *format, ...)
{
    va_list arg;
    int done;

    va_start (arg, format);
    done = vfprintf (stdout, format, arg);
    va_end (arg);

    return done;
}

Aynı dosyada bir makro bu işlevi olmayan altını printf için: bir diğer adı olarak tanımlanır, böylece bir ilişki kurar

ldbl_strong_alias (__printf, printf);

Printf stdout ile vfprintf çağıran ince bir tabaka olması mantıklı olurdu. Nitekim, biçimlendirme eserin et libc/stdio-common/vfprintf.c içinde bulacaksınız hangi vfprintf olarak hazırlanmış. Oldukça uzun bir işlevi var, ama hala C olduğunu görebilirsiniz!

Tavşan Deliği derine...

vfprintf gizemli garip makrolar aynı dosya içinde tanımlanan outchar ve outstring, çağırır:

#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)

Çok garip, neden soru gününü es geçtiğimiz, esrarengiz PUTC da aynı dosya içinde bağımlı olduğunu görüyoruz:

#define PUTC(C, F) IO_putwc_unlocked (C, F)

libc/libio/libio.h IO_putwc_unlocked tanımı geldiğinizde nasıl çalışır, basit bir beşgen kullanmaktır: artık umurunda olduğunu düşünmeye başlayabilir

#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)))

Ama okuması biraz zor olmasına rağmen, sadece tamponlu çıkış yapıyor. Eğer dosya işaretçisi tampon yeterli oda varsa, o zaman sadece karakter sopa olacak... ama eğer __woverflow çağırır. Tampon tükendi zaman tek seçenek ekranı temizlemek için (veya dosya işaretçisini temsil eder) olduğu için, sihirli sözler, orada bulmayı ümit edebiliriz.

C durumda fonksiyonun parametre değişkeni?

Eğer yönlendirme ile başka bir sinir bozucu seviye atlamak için gidiyoruz tahmin, haklısınız. Kütüphanenin/libio/wgenops bak.c ve __woverflow tanımını bulabilirsiniz:

wint_t 
__woverflow (f, wch)
    _IO_FILE *f;
    wint_t wch;
{
    if (f->_mode == 0)
        _IO_fwide (f, 1);
    return _IO_OVERFLOW (f, wch);
}

Temelde, dosya işaretçileri nesne olarak GNU standart kütüphane uygulanır. Veri üyesi var ama aynı zamanda ATLAMA makro varyasyonları ile görüşmesi yapabilirsiniz hangi üye işlevi. 38* *dosyası bu teknik, küçük bir belge

/* 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).
 */

libc/libio/genops.c IO_OVERFLOW bulduğumuzda biz bulmak dosya işaretçisi “1-parameter” __overflow bir yöntemi çağıran bir makro var:

#define IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)

Çeşitli dosya türleri için işaretçi atlama tabloları kütüphanenin/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)

Ayrıca bir var #_IO_file_overflow ve eski aynı kaynak dosyası olarak tanımlanır._IO_new_file_overflow eşittir tanımlamak (Not: İNTUSE sadece iç kullanım için fonksiyonları işaretleri olan bir makro, hiçbir şey ifade etmiyor “bu işlevi bir kesme kullanır”)

Daha gelmedik mi?!

_İO_new_file_overflow için kaynak kodu daha fazla tampon demet bir manipülasyon yok, ama _IO_do_flush çağrı yapıyor

#define _IO_do_flush(_f) \
    INTUSE(_IO_do_write)(_f, (_f)->_IO_write_base, \
        (_f)->_IO_write_ptr-(_f)->_IO_write_base)

_İO_do_write lastik aslında yolun birleştiği yerde muhtemelen nerede şimdi bir noktaya geldik: bir G/Ç aygıt için bir adet, gerçek, doğrudan yazma. En azından umut edebiliriz! _İO_new_do_write bir makro tarafından eşleştirilir ve bu var:

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;
}

Yine kaldık ne yazık ki... _IO_SYSWRITE işi

/* 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)

Bu do_write içindeki dosya işaretçisini üzerine yazma yöntemi diyoruz. _İO_new_file_write, o ne işe yarıyor? eşlenen atlama masamız yukarıdan biliyoruz

_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;
}

Artık sadece yazmak çağırıyor! Peki nerde bu uygulama? libc/posix/unistd.h yazmak

/* 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;

(Not: __wur __attribute__ ((__warn_unused_result__))) için bir makro

Fonksiyonları bir Tablo Oluşturulur

Sadece yazmak için bir prototip. Bir yaz bulamazsınız.GNU c standart kütüphane Linux için dosya. Bunun yerine, OS bağlanma platforma özgü yöntemler çeşitli şekillerde işlev yazma, kütüphanenin içinde bulabilirsiniz/sysdeps/ dizin.

Linux nasıl takip boyunca devam edeceğiz. Fonksiyonu otomatik olarak yazma oluşturmak için kullanılan bir dosya sysdeps/unix/syscalls.list denir. Tablodan ilgili veriler:

File name: write
Caller: “-” (i.e. Not Applicable)
Syscall name: write
Args: Ci:ibn
Strong name: __libc_write
Weak names: __write, write

Gizemli değil, Ci:ibn hariç. “”. İptal edilebilir C anlamına gelir Kolon ayıran dönüş tipinden bağımsız değişken türleri ve istersen daha derin bir açıklama yapmak ne demek o zaman görebilirsiniz yorum kabuk oluşturuyor kodu libc/sysdeps/unix/make-syscalls.sh.

Şimdi kütüphanenin_yazmak bu kabuk tarafından oluşturulan bir işlev __karşı bağlantı edebilmek için bekliyoruz. Ama oluşturulan nedir? Uygulayan bir makro ile yazmak olan bazı C kodu sysdeps/unix/sysdep içinde bulacaksınız hangi SYS_ify, denir.h

#define SYS_ify(syscall_name) __NR_##syscall_name

Ah, eski güzel token-yapıştırma :P. Yani temelde, bu uygulama bu __libc_write olur şey daha bir proxy çağırma sistem çağrısını fonksiyon bir parametre adında __NR_write ve diğer değişkenler.

Nerede Kaldırım Biter...

Bu büyüleyici bir yolculuk olduğunu biliyorum, ama şimdi GNU C kütüphanesi sonuna geldik. 64* *bu numarayı Linux ile tanımlanır. 32-bit x 86 mimarisi için linux/arch/x86/include/asm/unistd_32.h elde eder

#define __NR_write 4

Sadece bir şeye bakmak, sol, sonra, sistem çağrısı uygulanması. Bir noktada da yapabilirim, ama şimdilik sadece how to add a system call to Linux bazı başvurular için işaret edeceğim.

Bunu Paylaş:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • A Alkhaldi

    A Alkhaldi

    12 Mayıs 2007
  • Mary Jane Tauyan

    Mary Jane Ta

    20 AĞUSTOS 2009
  • oHeymarvin

    oHeymarvin

    11 Temmuz 2013