Linux C creating custom printf function without header libraries

1.8k views Asked by At

I am working on creating my own prinf() for a boot loader I am working on for a class assignment, this means I have to use the BCC compiler and I cannot use system libraries since they do not exist. I do have the ability to use the putc() function designed in assembly and the string library functions strcmp, etc to help me as needed.

I seem to have run into a logic issue.

If I define this in a test file compiled on Linux (cc):

int a = 0;
int b = 1;
int i1 = 0;
int i2 = 1;
unsigned long x = 42949672;
printf("\nHI! :%d: :%d: :%d: :%d: :%d: :%d:\n", x,a,b,i1,i2,x);

I can then run ./a.out and I receive HI! :42949672: :0: :1: :0: :1: :42949672: which is correct.

I've created my own printf function and when I see things printed I see HI! :23592: :655: :0: :1: :0: :1:, which is not correct. I've tried printing with integers only and it works fine, but when I try to print the unsigned long, I run into problems.

Here is my code:

void prints(s) char *s;
{
        int i;
        for(i=0; i<strlen(s); i++)
                putc(s[i]);
}

void gets(s) char *s;
{
        //LEC9.pdf
        while( (*s=getc()) != '\r')
        {
                putc(*s++);
        }
 *s = '\0';
}

//EXAMPLE CODE
char *ctable = "0123456789ABCDEF";
int rpi(x, BASE) unsigned long x; int BASE;
{
        char c;
        if (x)
 {
                c = ctable[x % BASE];
                rpi(x / BASE, BASE);
                putc(c);
        }
 return 0;
}

void printc(ip) unsigned long;
{
        putc(ip);
}
int printd(ip) unsigned long;
{
        if(ip < 0)
        {
                putc('-');
                ip = -ip;
        }
 if(ip == 0)
 {
  putc('0');
  return 0;
 }
 rpi(ip, 10);
}
void printx(ip) unsigned long;
{
        prints("0x"); //PUT OR OUTPUT LOOK LIKE INT
        rpi(ip, 16);
}
int printl(ip) unsigned long;
{
 if(ip == 0)
 {
  putc('0');
  return 0;
 }
        rpi(ip, 10);
        putc('L');
}
void printf(fmt) char *fmt;
{
        char *cp;               //POINTER TO LOOP THROUGH
        unsigned long *ip;     //POINTER FOR

        cp = fmt;               //SET POINTER TO START POINTER {FMT}
        ip = &fmt+1;            //Board says &fmt:but will not work without +1

        while(*cp)
        {
                //IF C != %
                if(*cp != '%')
                {
                        printf("%c", *cp);
                        if(*cp == '\n')
                        {
                                //putc('\n'); //implied
                                putc('\r');
                        }
                        cp++;
                        continue; //NEXT CHAR
                }
                else
                {
                        //MOVE ONE CHARACTER (%{x}) SO WE CAN GET x
                        cp++;
                        switch(*cp)
                        {
                                case 'c':
                                        printc(*ip);
                                        break;
                                case 's':
                                        prints(*ip);
                                        break;
                                case 'd':
                                        printd(*ip);
                                        break;
                                case 'x':
                                        printx(*ip);
                                        break;
                                case 'l':
                                        printl(*ip);
                                        break;
                                default:
                                        break;
                        }               }
                cp++;
                ip++;
        }
}

Anyone have any tips as I've been stuck and need some help.

EDIT (2:06pm): I've changed all my u16/unsigned short to unsigned long and things have changed to printing HI! :L: :0: :0: :: :: :1:

5

There are 5 answers

1
Alex W On BEST ANSWER

What architecture are you programming for?

If you are writing a boot loader for x86, then your boot loader will at first be in 16 bit mode. Thus when the compiler issues a push instruction, which is how I would guess it passes the arguments for the printf() function, it will by default push 16 bits of data. The long data type will be a specially issued instruction (or two) to push all 32 bits onto the stack, this is assuming an int is 16 bits and a long is 32 bits (which for a 16bit mode compiler, is not an unreasonable assumption, I don't think).

So, lets assume x86 in 16 bit mode:

It would appear you are using *ip to address the arguments as they are pushed on the stack. Since ip is a pointer to a long (a 32 bit data type) when you do ip++ you are incrementing the actual value held by the pointer by 4, as in if *ip = 0x1234 then *(ip+1) = 0x1238. Thus if you are using ip++ for the 16bit ints, then you are skipping an int every time you do ip++, since ints are only 2 bytes (16 bits). A possible solution is to use void * for ip and then add sizeof(data type) to ip; i.e if you print an int, so an:

void *ip = &(fmt + 1); /* Skip over fmt. */
...
ip += sizeof(int);

Or for an unsigned long:

ip += sizeof(unsigned long);

However, without more specific details about the exact architecture you are programming for and and what ABI your compiler is using, I can only wildly speculate.

I hope this helps.

Regards, Alex

3
James On

You're better off writing and verifying more basic test cases before you try so many % arguments.

1
codymanix On

Your rpi function is using u16 (unsigned short I guess) as data type which is far too small for an integer with the value 42949672.

3
jilles On

Assuming that "BCC" compiler is at least somewhat modern, you should use its <stdarg.h> macros va_start, va_arg and va_end to access the variable arguments.

Modern C also requires a full prototype with ... for vararg functions.

1
thkala On

Here's a hint:

>>> hex(42949672)
'0x28f5c28'
>>> hex(23592)
'0x5c28'

Somewhere along the way you use a shorter type. I'd check your use of u16, for example.