原创文章,转载写明出处
作者Crazii @ CSDN
我看了minix的内核printf函数之后,发现它里面的goto有语句...我就读不下去了,于是自己写了一个.kprintf
首先要了解的就是printf的变参.这得从c调用规则讲起.回顾一下c(cdecl)调用规则(vc里面__cdecl关键字,MASM里面的.model c)除了命名要加下划线之外,还有函数的调用:参数是从右至左入栈,并且是调用者恢复栈.
;printf(fmt,s);假定fmt和s是指针变量
push s
push fmt
call _printf
add esp 8
嗯,这里还有一个问题,就是栈对齐问题.如果是char型参数,或者是short型参数,那么压栈的数据宽度是多少?16位下我们知道,最小得是一个word,也就是short型,char型数据入栈时,也是按word.而32位下,压栈的默认大小跟段描述符里面的描述有关,如果段是16位段,则按16位入栈,如果是32位段,则按32位入栈.这里当然要说32位的栈了,push 0xFF,这样的立即数是按32位对齐,push ds这样的16位段寄存器也是按32位对齐.好了.ok到这里,感觉离题有点远了..
正因为cdecl调用跟stdcall不同,它是调用者负责恢复栈的,所以调用者想压几个参数就压几个参数,调用完了自己在平衡堆栈就OK了,所以可以支持变参,但是stdcall是被调用函数在结束时ret n来维护栈的,调用者不能随便压参数.总之,cdecl支持可变参数vararg,所以才有现在的printf
嗯,根据C调用规则,就知道了栈上的参数分布,这样在读取可变参数时,就好办了.
- /******************************************************
- stdarg.h
- standard argument header for NGOS
- *******************************************************
- C语言参数头文件
- Author: ZZU-Crazii
- */
- typedef char *var_list; //VC里面也是这样写的,跟我想的一样
- /* 参考VC里的头文件,栈的对齐,是sizeof(int)的整数倍 */
- #define INT_SIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
- /* 按对齐,取下一个参数 */
- #define var_start(arg_ptr,arg_list) ( (void) (arg_ptr = (var_list)(&arg_list)+INT_SIZEOF(arg_list)) )
- /* 将取出的参数强制转换为指定的Type,并指向下一个参数 */
- #define var_arg(arg_ptr,type) (*(type*) ( (arg_ptr +=INT_SIZEOF(type))-INT_SIZEOF(type) ) )
- #define var_end(arg_ptr) ((void)(arg_ptr=NULL)
需要说明的是,我自己写的stdarg之前的版本,栈取参数后的增量是++操作(+sizeof(type)),那么如果取出的类型是char,则varptr是+1的,这显然不符合栈对齐原则.应该将sizeof(type)对齐到INT长度的倍数,就像VC的crt头文件里面一样.这样,如果取出的参数是char,那么varptr将+4,来指向下一个参数好了,可以看kprintf了:
- //这个宏来自minix
- #define isdigit(c) ( (unsigned)( (unsigned)(c) - '0') <= (unsigned)9 )
- #define BUFFER_SIZE 32 /* 将数字转化为字符串的缓冲 */
- /*****************kprintf*************************/
- #include <stdarg.h> /* 自己做的头文件 */
- /* 支持%s, %d,%u 的简单版*/
- /* 3.26,添加%x,长度,对齐.等 */
- /* 3.26 稍作修改,添加%X */
- void kprintf(const char *fmt,...)
- {
- var_list argp;
- int c;
- /*const char *pString;*/
- const char *Ostring;
- union
- {
- int s; /* 有符号 */
- unsigned uns; /* 无符号 */
- }num;
- int i;
- unsigned int len;
- static char tmp[BUFFER_SIZE];
- static const char null_pointer[] ="(NULL)";
- int width; /* 指定显示长度 */
- unsigned int limit; /* 截断显示长度 */
- int align_left;
- char fill_prefix;
- if( fmt == NULL ) return;
- var_start(argp,fmt);
- while( (c=*(fmt++)) !=NULL )
- {
- if( c!='%' )
- {
- kputchar(c);
- continue;
- }
- num.uns = 0;
- Ostring = NULL;
- c = *(fmt++); /* 跳过 '%' */
- /* 对齐方式 */
- align_left = FALSE;
- if( c == '-' )
- {
- align_left = TRUE;
- c = *(fmt++);
- }
- /* '0'前缀 */
- fill_prefix =' ';
- if( c == '0')
- {
- fill_prefix = '0';
- c = *(fmt++);
- }
- /* 宽度 */
- width = 0;
- if( c == '*' )
- {
- width = var_arg(argp,int);
- c = *(fmt++);
- }
- else while ( isdigit(c) )
- {
- width= width * 10 + (c - '0');
- c = *(fmt++);
- }
- /* 截断 */ /* 修复BUG,limit=0,串不能显示 */
- limit = ULONG_MAX;
- if( c == '.' )
- {
- c = *(fmt++);
- if( isdigit(c) )
- {
- limit = 0;
- while( isdigit(c) )
- {
- limit= limit * 10 + (c - '0');
- c = *(fmt++);
- }
- }
- }
- switch(c)
- {
- case 's': /* %s */
- Ostring=var_arg(argp,char*);
- if( Ostring == NULL )
- Ostring=null_pointer;
- /* 计算字符串长度 */
- for( i=0; Ostring[i]!=0; i++);
- len = i;
- break;
- case 'd': /* %d */
- num.s=var_arg(argp,int);
- if(num.s==0)
- {
- tmp[0] ='0';
- Ostring = tmp;
- len =1;
- break;
- }
- if( num.s<0 ) /* 将负数转换成正数 */
- {
- kputchar('-');
- num.s = -num.s;
- }
- /* !!!没有break,继续,作为无符号数处理 */
- case 'u': /* %u */
- if( num.uns == 0 ) num.uns = var_arg(argp,unsigned int);
- if(num.uns==0)
- {
- tmp[0] ='0';
- Ostring = tmp;
- len =1;
- break;
- }
- /* 3.26 改成从高字节(BUFFER_SIZE-1)开始写入字符 */
- for(i=0; num.uns/10 !=0 || num.uns%10 !=0;num.uns/=10,i++)
- tmp[BUFFER_SIZE-1-i] = (char)(num.uns%10) + '0';
- /****************************
- 字符串: H=结束字节,第一个写入,
- L=开始字节,最后一个被写入,
- B=缓冲区开始位置
- |<-长度i->|
- |B|.....|L|.....|H|
- ^
- |
- (BUFFER_SIZE-i)索引的最终位置
- *****************************/
- Ostring = tmp+ (BUFFER_SIZE - i);
- len = i;
- /* 数字不做截断处理 */
- limit = ULONG_MAX;
- break;
- case 'x': case'X':
- num.uns = var_arg(argp,unsigned int);
- if(num.uns==0)
- {
- tmp[0] ='0';
- Ostring = tmp;
- len =1;
- break;
- }
- for(i=0; num.uns/16 !=0 || num.uns%16 !=0;num.uns/=16,i++)
- {
- if( (num.uns%16) < 10 )
- tmp[BUFFER_SIZE-1-i] = (char)(num.uns%16) + '0';
- else /* 'x'-'a' == 'X'-'a' 嘎嘎~ */
- tmp[BUFFER_SIZE-1-i] = (char)((num.uns%16)-10)+c-('x'-'a');
- }
- Ostring = tmp+ (BUFFER_SIZE - i);
- len = i;
- /* 数字不做截断处理 */
- limit = ULONG_MAX;
- break;
- default:
- kputchar('%');
- kputchar(c);
- break;
- }
- /* 问题:数字缓冲是倒序,但是字符串是正序排列... */
- /* 把数字缓冲改成正序.OK */
- if( Ostring != NULL )
- {
- /* 计算对齐并输出 */
- if( !align_left && len< width )
- {
- do
- {
- kputchar(fill_prefix);
- width--;
- }while( len<width );
- }
- /* 截断处理 */
- if( len > limit )
- len = limit;
- for(i=0;i<len;i++)
- kputchar( Ostring[i] );
- if( align_left && len< width )
- {
- do
- {
- kputchar(' ');
- width--;
- }while( len<width );
- }
- }
- }
- var_end(argp);
- kputchar( NULL ); /* 释放缓冲,显示 */
- }