现在做个假设,如果自己用c语言写一段hello world程序,用armlinux-gcc编译器编译好,然后用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配置完毕,然后么,跳转到可执行代码段,剩下的就是机器自己的事情了。
为完待续。。。
下回将分析一下,操作系统是如何运行一个文件的?需要做哪些前期工作?可执行文件里面包含了什么秘密?
文章来源于领测软件测试网 https://www.ltesting.net/