Search code examples
clinuxbcc-compiler

Linux C creating custom printf function without header libraries


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:


Solution

  • 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