現在做個假設,如果自己用c語言寫一段hello world程序,用armlinux-gclearcase/" target="_blank" >cc編譯器編譯好,然后用jtag燒寫到板子得flash當中,程序是否可以運行?答案是相當否定的。因為這個時候,沒有對底層的硬件做任何的初始化,可以通過匯編,直接對串口進行操作,但是對于gcc的printf,畢竟也是一個高級的包裝函數,現在根據gcc的源代碼順藤摸瓜,看看具體是怎么實現的?
//printf.c
int
printf (const char *string, ...)
{
va_list ap;
int r;
#ifdef __OPTIMIZE__
if (inside_main)
abort();
#endif
va_start (ap, string);
r = vprintf (string, ap);
va_end (ap);
return r;
}
//vprintf.c
nt
vprintf (format, ap)
const char *format;
va_list ap;
{
return vfprintf (stdout, format, ap);
}
//FILE *stdout
typedef struct _iobuf
{
char* _ptr;
int _cnt;
char* _base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char* _tmpfname;
} FILE;
//vfprintf.c
int
vfprintf (stream, format, ap)
FILE * stream;
const char * format;
va_list ap;
{
return _doprnt (format, ap, stream);
}
//_doprnt.c
int
_doprnt (format, ap, stream)
const char * format;
va_list ap;
FILE * stream;
{
const char * ptr = format;
char specifier[128];
int total_printed = 0;
while (*ptr != '')
{
if (*ptr != '%') /* While we have regular characters, print them. */
PRINT_CHAR(*ptr);
else /* We got a format specifier! */
{
char * sptr = specifier;
int wide_width = 0, short_width = 0;
*sptr++ = *ptr++; /* Copy the % and move forward. */
while (strchr ("-+ #0", *ptr)) /* Move past flags. */
*sptr++ = *ptr++;
if (*ptr == '*')
COPY_VA_INT;
else
while (ISDIGIT(*ptr)) /* Handle explicit numeric value. */
*sptr++ = *ptr++;
if (*ptr == '.')
{
*sptr++ = *ptr++; /* Copy and go past the period. */
if (*ptr == '*')
COPY_VA_INT;
else
while (ISDIGIT(*ptr)) /* Handle explicit numeric value. */
*sptr++ = *ptr++;
}
while (strchr ("hlL", *ptr))
{
switch (*ptr)
{
case 'h':
short_width = 1;
break;
case 'l':
wide_width++;
break;
case 'L':
wide_width = 2;
break;
default:
abort();
}
*sptr++ = *ptr++;
}
switch (*ptr)
{
case 'd':
case 'i':
case 'o':
case 'u':
case 'x':
case 'X':
case 'c':
{
/* Short values are promoted to int, so just copy it
as an int and trust the C library printf to cast it
to the right width. */
if (short_width)
PRINT_TYPE(int);
else
{
switch (wide_width)
{
case 0:
PRINT_TYPE(int);
break;
case 1:
PRINT_TYPE(long);
break;
case 2:
default:
#if defined(__GNUC__) || defined(HAVE_LONG_LONG)
PRINT_TYPE(long long);
#else
PRINT_TYPE(long); /* Fake it and hope for the best. */
#endif
break;
} /* End of switch (wide_width) */
} /* End of else statement */
} /* End of integer case */
break;
case 'f':
case 'e':
case 'E':
case 'g':
case 'G':
{
if (wide_width == 0)
PRINT_TYPE(double);
else
{
#if defined(__GNUC__) || defined(HAVE_LONG_DOUBLE)
PRINT_TYPE(long double);
#else
PRINT_TYPE(double); /* Fake it and hope for the best. */
#endif
}
}
break;
case 's':
PRINT_TYPE(char *);
break;
case 'p':
PRINT_TYPE(void *);
break;
case '%':
PRINT_CHAR('%');
break;
default:
abort();
} /* End of switch (*ptr) */
} /* End of else statement */
}
return total_printed;
}
#define PRINT_CHAR(CHAR) \
do { \
putc(CHAR, stream); \
ptr++; \
total_printed++; \
continue; \
} while (0)
//System.h
# define putc(C, Stream) putc_unlocked (C, Stream)
//2005-06-22
剛才下載了2.95的gcc,發現了可以繼續探索下去
//putc_u.h
int
putc_unlocked (c, fp)
int c;
_IO_FILE *fp;
{
CHECK_FILE (fp, EOF);
return _IO_putc_unlocked (c, fp);
}
//libio.h
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
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 _blksize;
#ifdef _G_IO_IO_FILE_VERSION
_IO_off_t _old_offset;
#else
_IO_off_t _offset;
#endif
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
char _unused;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
#ifdef _IO_LOCK_T
_IO_LOCK_T _lock;
#endif
#if defined(_G_IO_IO_FILE_VERSION) && _G_IO_IO_FILE_VERSION == 0x20001
_IO_off64_t _offset;
int _unused2[16]; /* Make sure we don't get into trouble again. */
#endif
};
#define _IO_putc_unlocked(_ch, _fp) \
(((_fp)->_IO_write_ptr >= (_fp)->_IO_write_end) \
? __overflow (_fp, (unsigned char) (_ch)) \
: (unsigned char) (*(_fp)->_IO_write_ptr++ = (_ch)))
繼續...
int
__overflow (f, ch)
_IO_FILE *f;
int ch;
{
return _IO_OVERFLOW (f, ch);
}
//libiop.h
/* The 'overflow' hook flushes the buffer.
The second argument is a character, or EOF.
It matches the streambuf::overflow virtual function. */
typedef int (*_IO_overflow_t) __PMT ((_IO_FILE *, int));
#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
//go on...
# define JUMP1(FUNC, THIS, X1) _IO_JUMPS(THIS)->FUNC (THIS, X1)
#define _IO_JUMPS(THIS) ((struct _IO_FILE_plus *) (THIS))->vtable
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};
struct _IO_jump_t
{
JUMP_FIELD(_G_size_t, __dummy);
#ifdef _G_USING_THUNKS
JUMP_FIELD(_G_size_t, __dummy2);
#endif
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);
#if _G_IO_IO_FILE_VERSION == 0x20001
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
#endif
#if 0
get_column;
set_column;
#endif
};
哈,終于找到了最終的函數_IO_seek_t ,__write,根據此處的經驗來看,應該為操作系統實現的。
費了這么大一半天,只是想說明,gcc編譯出來的東西,其依賴于操作系統的實現,也即是說,gcc和操作系統是相輔相成的,操作系統負責提供一些最底層的基本的函數,而gcc則把這些東西包裝起來。
具體細節,在編操作系統的時候,例如定制自己的mystdio.h,里面實現__swbuf函數,然后就可以調用printf啦
不過有點迷惑,是如何商量好__swbuf函數的?莫非還能繼續分解下去?還請通曉之人指教以下~
其實說到最后,無論elf還是exe格式,說到底都是2進制文件,它們的差別,就在于操作系統的實現差別,操作系統裝入這些可執行文件的text段啊,動態連接庫等等的不同,導致了各個文件只能在其自己的系統上運行,而linux下面的wine軟件,正是看到了這點,把windows對于exe文件的前期配置按照linux配置完畢,然后么,跳轉到可執行代碼段,剩下的就是機器自己的事情了。
為完待續。。。
下回將分析一下,操作系統是如何運行一個文件的?需要做哪些前期工作?可執行文件里面包含了什么秘密?