Home > Software engineering >  va_args not accepting signed integer in C
va_args not accepting signed integer in C

Time:04-05

I was designing my kernel using C. While making the kprintf function (a function like printf but works with the kernel) I saw that signed integers (precisely the data type is long), va_args is converting them to unsigned long.

Here's a snippet of the code: kPrint.c

#include <stdarg.h>

// skipped some lines not needed for this question
// ...

/******************
 * global variables needed for number to string conversion
 * ***************/
// store the converted string temporarily
static char convertedString[30];

// store the index of the filled portion
static char numberIndex;

// TODO: Create function like printf but which works here
_Bool kprintf(const char* stringFormat, ...) {
    char* strPtr = (char*)stringFormat;
    va_list arguements;
    va_start(arguements, stringFormat);

    while (*strPtr != 0) {

        switch (*strPtr)
        {
        case '\e':
            // printf() also ends the execution
            return 1;
        case '%':
            switch (*(strPtr   1))
            {
            // signed decimal integer
            case 'd':
            case 'i':
                kprintf(IntToString(va_arg(arguements, long long))); // stringify the integer
                // *problem* here
                strPtr  = 2;
                break;

            // unsigned decimal integer
            case 'u':
                kprintf(uIntToString(va_arg(arguements, uint64_t))); // stringify
                strPtr  = 2;
                break;

            // will implement U & L case later
            // unsigned hexadecimal
            case 'x':
            case 'X':
                kprintf(uHexToString(va_arg(arguements, uint64_t))); // stringify
                // doesn't work now
                strPtr  = 2;
                break;

            // will implement U & L case later
            // 6 numbers after decimal
            case 'f':
            case 'F':
                kprintf(DoubleToString(va_arg(arguements, double), 6)); // stringify
                // doesn't work now
                strPtr  = 2;
                break;

            // 2 numbers after pt
            case 'g':
            case 'G':
                kprintf(DoubleToString(va_arg(arguements, double), 2));
                strPtr  = 2;
                break;

            case 'c':
                kPrintChar((char)va_arg(arguements, int));
                //
                strPtr  = 2;
                break;

            // another string
            case 's':
                kPrintfHelper(va_arg(arguements, char*)); // just to prevent recursion
                strPtr  = 2;
                break;

            case '%':
                kPrintChar('%');
                strPtr  = 2;
                break;
            }
            break;
        default:
            kPrintChar(*strPtr);
            strPtr  ;
        }
    }
    va_end(arguements);
    return 0;
}

void uIntToStrHelper(uint64_t num) {
    if (num < 10)
    {
        convertedString[numberIndex  ] = num   '0';
        convertedString[numberIndex] = 0;
        return;
    }

    uint8_t numIndexCpy = numberIndex;

    while (num > 0) {
        convertedString[numberIndex  ] = (num % 10)   '0';
        num /= 10;
    }

    char swpIndex = (numberIndex - 2   numIndexCpy) / 2;
    numberIndex = numberIndex - swpIndex - 1   numIndexCpy;

    while (swpIndex >= numIndexCpy) {
        convertedString[swpIndex] = convertedString[swpIndex]   
            convertedString[numberIndex];

        convertedString[numberIndex] = convertedString[swpIndex] -
            convertedString[numberIndex];

        convertedString[swpIndex] = convertedString[swpIndex] -
            convertedString[numberIndex];
        
        swpIndex--;
        numberIndex  ;
    }

    convertedString[numberIndex] = 0;
}

char* uIntToString(uint64_t num) {
    numberIndex = 0;
    uIntToStrHelper(num);
    return convertedString;
}

char* IntToString(long long num) {
    numberIndex = 0;
    if (num < 0) {
        convertedString[numberIndex  ] = '-';
        num = -num;
    }

    uIntToStrHelper(num);
    return convertedString;
}

EDIT: Added IntToString and uIntToString.

(I don't know the correct way to do this, but it suffices)

Overview of the problem: cases for 'd' and 'i' show the place of the problem.
Function prototype of IntToString:

char* IntToString(long long num);
// part of the reason why I used long long as va_arg data type
// had also tried with long but with no luck :(

I have tried IntToString & uIntToString functions on a linux machine and they work the way intended.

NOTE: I have not tested kprintf(const char* stringFormat, ...) in a controlled & easily debuggable environment like the other 2 functions mentioned just above.

Other functions like DoubleToString and uHexToString have not yet been tested but it should not change/be related to the question anyway.

kPrintfHelper is a function just like kprintf but does not do any checking and just prints the string

My Environment
Compiler: x86_64-elf-gcc
Flags (aka CFLAGS): -ffreestanding -mno-red-zone -m64

Linker: x86_64-elf-ld

I use qemu-system-x86_64 to run the final executable.

Tests

I have tried the following cases:

  1. Normal unsigned case
kprintf("Number: %d\n", 1234);

Output:

Number: 1234

  1. Does not work but the output seems like va_args is doing some weird unsigned int math (further proved by test 5 that it was indeed va_args, IMO)
kprintf("Number: %d\n", -1234);

Output:

Number: 4294966062

  1. Works as intended
kprintf("Number: %d\n", 1099511627775);

Output:

Number: 1099511627775

  1. BUT This
kprintf("Number: %d\n", -1099511627775);

Output:

Number: 1099511627775

  1. AND here
kprintf("Number: %s\n", IntToString(-1234));

Output:

Number: -1234

CodePudding user response:

The type of the value you're passing in for your %d specifier doesn't match what va_arg is expecting.

You're telling va_arg to expect a long long, but 1234 and -1234 have type int. These types are different sizes, so va_arg is pulling more bytes from the call stack than you put on.

The standard format specifiers have size modifiers so they can handle different size types. You'll need to implement something similar.

CodePudding user response:

The call kprintf("Number: %d\n", -1234); is incorrect because %d extracts a long long. It must be kprintf("Number: %d\n", -1234LL);.

-1234 is a 32 bit operand. The problem could be that this is being passed in a 64 bit aligned word, but not being sign extended to 64 bits.

So that is to say, the -1234 value in 64 bits needs to be fffffffffffffb2e, but the 32 bit parameter is producing a 00000000fffffb2e image on the stack, which is 4294966062.

According to this hypothesis, we would have to pass -1000 to obtain the observed 429496629, however. It bears no relation to -1234. Something else could be going on, like garbage bits being interpreted as data.

The behavior is not well-defined, after all: you're shoving an integer of one size into a completely typeless and unsafe parameter passing mechanism and pulling out an integer of a different size.

  • Related