G-bios中printf的实现分析
Hyfeng E-mail:hyfeng18@126.com
以前不论是在linux或window下写C程序,我一直都很喜欢使用printf来调试错误,基本上就没怎么用过debug去调试错误,个人觉得printf调试错误比较的方便,而且很多程序用debug是没办法调试的,因此我还是很喜欢通过观察输出来判断错误。可是现在转到嵌入式平台上面开发程序,没有了最爱的printf有的时候还真不知道怎么去调试程序了,之前一直都会用ads或者keil中debug功能跟踪调试程序,不过说真话我是真的不习惯用。不过庆幸的是很多嵌入式设备都有串口,有串口就可以输出字符,也就可以当做输出调试的接口了,上次的在LPC1700上面调试LWIP的时候就是通过在串口上面输入程序的执行流程,最后很容易就定位到网卡硬件初始化函数不正确,知道错误的函数改起来也就比较方便了。可是通过简单的串口发送程序调试是有一点缺陷的,就是一般情况下如果没有对串口发送函数做特殊的处理的话,只能输出字符串这样调试起来就没有那么方便了。
虽然觉得只能输出字符串调试很不方便,但也没有想去改进它,直到前几天移植g-bios的时候看到了程序中提供了一个stdio.c的文件,在这个文件中就实现了将printf的输出设备变成了串口,也就是标准C中的printf是输出在屏幕上的,而通过修改以后的pirntf是通过串口输出的,这样一来在调试嵌入式程序的时候我就可以使用printf来输出调试信息了。不过嵌入式设备中printf和标准C的还是有些差别的,简单的说嵌入式开发中使用的printf是标准C使用的printf的阉割版,为什么下面分析程序的时候会讲到。
先看一下printf函数
- int printf(const char *pchFmt, ...)
-
{
-
/*获得第二个参数的地址*/
-
int *nPara = (int *)&pchFmt + 1;
-
int nPrtedlen;
-
char chArraybuf[512];
-
char *pchBuf = chArraybuf;
-
-
nPrtedlen = vsprintf(chArraybuf, pchFmt, nPara);
-
-
while (*pchBuf)
-
putchar(*pchBuf++); //通过在putchar函数调用串口输出函数,就可实现printf在串口上面输出了。
-
-
return nPrtedlen;
-
}
这里printf是一个可变长参数函数,参数pchFmt保存的是参数的地址,在printf中第一个参数就是那个字符串,而且参数值就是从第二个参数开始的。从上面的函数可以看出printf相对比较的简单,而大部分的解析字符的工作都放在了vsprintf函数中完成,在看vsprintf之前我觉得又必要了解一下printf对于参数输出格式的定义,printf中输出定义格式如下:
%[flags][width][.perc][F|N|h|l]type
(1). Type就是指输出值的类型,例如:d有符号10进制整形,等等。。。
(2). Flags规定输出格式,取值和含义如下:
无 右对齐,左边填充0和空格
-
左对齐,右边填充空格
+ 在数字前增加符号+或-
0
将输出的前面不上0,直到占满指定列宽为止(不可以搭配使用-)
空格 输出值为正时冠以空格,为负时冠以负号
#当type=o,x,X时,分别在数值前增加‘0‘,’0x‘,’0X‘
(3) width用于控制显示数值的宽度,取值和含义如下:
n表示宽度至少为n位,不够以空格填充
0n表示宽度至少为n为,不够左边以0填充
*格式列表中,下一个参数还是width
(4) prec用于控制小数点后面的位数,在嵌入式应用中一般不处理浮点型的数据,这个就不介绍了
(5) F|N|h|l表示指针是否是远指针或整数是否是长整数
vsprintf函数的主要功能就是按照这个格式去解析字符串,并将参数替换到%对于的地方,vsprintf的源码如下:
- #define LEFT 0x1
-
#define SIGN 0x2
-
#define SPACE 0x4
-
#define PLUS 0x8
-
#define ZERO 0x10
-
#define SIGNINT 0x20
-
/*
-
* Format的格式 ==> %[flags][width][.perc][F|N|h|l]type
-
*/
-
static int vsprintf(char *pchBuf, const char *pchFormat, int *pPara)
-
{
-
int nOutputStyle, nWidth, nBase;
-
const char *pchFmtRoll;
-
char *pchStrTemp;
-
int nStrLen;
-
char chTempArray[32];
-
int nCount = 0;
-
char *pchBufTemp = pchBuf;
-
-
-
while (*pchFormat)
-
{
-
/*不是%符号不需要解析*/
-
if ('%' != *pchFormat)
-
{
-
*pchBufTemp++ = *pchFormat++;
-
continue;
-
}
-
pchFmtRoll = pchFormat++;
-
/*连续出现%那么从第二%开始输出*/
-
if ('%' == *pchFormat)//handle e.g "%%"
-
{
-
*pchBufTemp++ = *pchFormat++;
-
continue;
-
}
-
pchFormat--;
-
-
/*解析flags*/
-
nOutputStyle = 0;
-
get_sign :
-
pchFormat++;
-
switch(*pchFormat)
-
{
-
case '-' :
-
/*输出格式:左对齐,右边以空格填充*/
-
nOutputStyle |= LEFT;
-
nOutputStyle &= (~ZERO);
-
goto get_sign;
-
case '+' :
-
/*输出格式:在数字前面增加+或-*/
-
nOutputStyle |= SIGN;
-
goto get_sign;
-
case ' ' :
-
/*输出格式:输出值为正时加空格,输出值为负数加-*/
-
nOutputStyle |= SPACE;
-
goto get_sign;
-
case '#' :
-
/*输出格式:对16进制和8进制添加相应的前缀*/
-
nOutputStyle |= PLUS;
-
goto get_sign;
-
case '0' :
-
/*输出格式:将输出值前面补0直到暂满width为止*/
-
if (!(nOutputStyle & LEFT))
-
nOutputStyle |= ZERO;
-
goto get_sign;
-
}
-
-
/*解析width*/
-
nWidth = 0;
-
/*解析数字的值*/
-
while (ISDIGIT(*pchFormat))
-
{
-
nWidth = nWidth * 10 + *pchFormat - '0';
-
pchFormat++;
-
}
-
-
//handle '*',replace '*'
-
if ('*' == *pchFormat)
-
{
-
pchFormat++;
-
-
if (!ISDIGIT(*pchFormat))
-
{
-
nOutputStyle &= ~LEFT;
-
nWidth = *pPara++;
-
}
-
else
-
{
-
while (*pPara)
-
{
-
chTempArray[nCount++] = *pPara%10 + '0';
-
*pPara /= 10;
-
}
-
-
while (nCount)
-
(*pchBufTemp++) = chTempArray[--nCount];
-
-
pPara++;
-
continue;
-
}
-
}
-
-
nBase = 10;
-
/*下面是对于格式中type字段的解析,主要就设定一下输出的格式,而且对于字符串和字符是直接放到最后的输出串当中的,而对于非字符的,例如:整形,是需要将整数转换成字符的,这个是由NumToAscii完成的*/
-
//fixme:handle '%l(o,x,i,d)' and '%ll(o,x,i,d)',filter 'l' and 'll'
-
if (('l' == *pchFormat) && ('l' == *++pchFormat))
-
*pchFormat ++;
-
-
switch(*pchFormat)
-
{
-
case 'u':
-
nBase = 10;
-
nOutputStyle &= ~(SIGN | PLUS);
-
break;
-
-
case 'd' :
-
case 'i' :
-
nBase = 10;
-
if (!(nOutputStyle & SIGN))
-
nOutputStyle |= SIGNINT | SIGN;
-
// nOutputStyle &= (~PLUS);
-
break;
-
case 'p' :
-
nOutputStyle |= PLUS;
-
case 'X' :
-
case 'x' :
-
nBase = 16;
-
break;
-
case 'o' :
-
nBase = 8;
-
break;
-
case 's' :
-
pchStrTemp = (char *)*pPara;
-
nStrLen = strlen(pchStrTemp);
-
if (!(nOutputStyle & LEFT))
-
{
-
if (nOutputStyle & ZERO)
-
{
-
while (nWidth-- > nStrLen)
-
*pchBufTemp++ = '0';
-
}
-
else
-
{
-
while (nWidth-- > nStrLen)
-
*pchBufTemp++ = ' ';
-
}
-
}
-
while (*pchStrTemp)
-
*pchBufTemp++ = *pchStrTemp++;
-
while (nWidth-- > nStrLen)
-
*pchBufTemp++ = ' ';
-
pPara++;
-
pchFormat++;
-
continue;
-
case 'c' :
-
if (!(nOutputStyle & LEFT))
-
{
-
if (nOutputStyle & ZERO)
-
{
-
while (nWidth-- > 1)
-
*pchBufTemp++ = '0';
-
}
-
else
-
{
-
while (nWidth-- > 1)
-
*pchBufTemp++ = ' ';
-
}
-
}
-
*pchBufTemp++ = (char)*pPara;
-
while (nWidth-- > 1)
-
*pchBufTemp++ = ' ';
-
pPara++;
-
pchFormat++;
-
continue;
-
default :
-
*pchBufTemp++ = *pchFmtRoll++;
-
pchFormat = pchFmtRoll;//roll back pchFormat
-
continue;
-
}
-
pchBufTemp = NumToAscii(pchBufTemp, *pPara, nWidth, nBase, nOutputStyle);
-
pPara++;
-
pchFormat++;
-
}
-
*pchBufTemp = '\0';
-
return pchBufTemp - pchBuf;
-
}
对于上面的程序switch中的continue是对外面的循环使用的,并不是和break一样只是简单跳过下面case的选项,而是跳过while循环中的下面未执行的部分,这种用法我是第一次看到的,刚开始还没理解它的意思以为作用与break一样的呢?
对于下面的NumToAscii函数就是按照上面解析的格式将数字转换成对于的格式,最后全部都转换成字符的形式输出到相应的地方,下面就给NumToAscii的代码吧
- static char * NumToAscii(char *pBuf, \
-
unsigned int nNum, \
-
int nWidth, \
-
int nBase, \
-
int nOutputStyle)
-
{
-
char chTempArray[32], chFillChar, chSign = '\0';
-
const char *pchDigit = "0123456789abcdef";
-
int nCount = 0;
-
int nPrefixNum = 0;
-
-
-
chFillChar = (nOutputStyle & ZERO) ? '0' : ' ';//set ZERO attribute
-
-
//set SIGN and SPACE
-
if (nOutputStyle & SIGN && (10 == nBase))
-
{
-
/*有符号的整数,最高位为1是负数*/
-
if (nNum & 0x1 << 31)
-
{
-
chSign = '-';
-
nNum = -nNum;
-
}
-
else if (!(nOutputStyle & SIGNINT))
-
{
-
chSign = '+';
-
}
-
}
-
else if (!((nOutputStyle & SIGN) || (nNum & 0x1 << 31)) && (10 == nBase) && (nOutputStyle & SPACE))
-
chSign = ' ';
-
-
//if num == 0
-
if (nNum == 0)
-
chTempArray[nCount++] = '0';
-
-
//convert number
-
while (nNum != 0)
-
{
-
chTempArray[nCount++] = pchDigit[nNum % nBase];
-
nNum = nNum / nBase;
-
}
-
-
//set PLUS
-
if (nOutputStyle & PLUS)
-
{
-
if (nBase == 16)//handle hex
-
nPrefixNum = 2;
-
if (nBase == 8)//handle oct
-
nPrefixNum = 1;
-
}
-
-
//EccGenerate number of chFillChar
-
nWidth = nWidth - nPrefixNum - (chSign ? 1 : 0) - nCount;
-
-
if (!(nOutputStyle & LEFT))
-
{
-
while (nWidth-- > 0)
-
*pBuf++ = chFillChar;
-
}
-
-
switch(nPrefixNum)
-
{
-
case 2:
-
*pBuf++ = '0';
-
*pBuf++ = 'x';
-
break;
-
case 1:
-
*pBuf++ = '0';
-
break;
-
default :
-
break;
-
}
-
-
if (chSign)
-
*pBuf++ = chSign;
-
-
while (nCount > 0)
-
*pBuf++ = chTempArray[--nCount];
-
-
while (nWidth-- > 0)
-
*pBuf++ = chFillChar;
-
-
return pBuf;
-
}
一个比较简单的printf函数算是完成了,这几天一直在看<<0bug-c/c++商用工程之道>>它上面也谈到了一些关于debug调试的内容,觉得他讲的的还是很不错的,书上说要建立自己的工程库,我觉得这个还是可以去慢慢做的,毕竟以后可能一直都要做计算机这一行的。要是想看一下具体的源码可以去下载g-bios的源码,对于g-bios其实很多东西和uboot是很像的不过个人觉得g-bios更适合初学的人看,毕竟先g-bios只支持几种芯片,在程序方面并不像uboot一个函数当中有一堆的宏,通过宏的定义来实现对于不同功能的支持看的真的比较的麻烦,g-bios的源码看的还是比较简洁的就是文档少了点,而且程序基本没什么注释的幸亏程序命名是按照匈牙利命名法写的,根据函数名你还能知道这个函数的大体意思。
阅读(2037) | 评论(0) | 转发(0) |