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 arguments;
va_start(arguments, 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(arguments, long long))); // stringify the integer
// *problem* here
strPtr += 2;
break;
// unsigned decimal integer
case 'u':
kprintf(uIntToString(va_arg(arguments, uint64_t))); // stringify
strPtr += 2;
break;
// will implement U & L case later
// unsigned hexadecimal
case 'x':
case 'X':
kprintf(uHexToString(va_arg(arguments, 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(arguments, double), 6)); // stringify
// doesn't work now
strPtr += 2;
break;
// 2 numbers after pt
case 'g':
case 'G':
kprintf(DoubleToString(va_arg(arguments, double), 2));
strPtr += 2;
break;
case 'c':
kPrintChar((char)va_arg(arguments, int));
//
strPtr += 2;
break;
// another string
case 's':
kPrintfHelper(va_arg(arguments, char*)); // just to prevent recursion
strPtr += 2;
break;
case '%':
kPrintChar('%');
strPtr += 2;
break;
}
break;
default:
kPrintChar(*strPtr);
strPtr++;
}
}
va_end(arguments);
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.
I have tried the following cases:
kprintf("Number: %d\n", 1234);
Output:
Number: 1234
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
kprintf("Number: %d\n", 1099511627775);
Output:
Number: 1099511627775
kprintf("Number: %d\n", -1099511627775);
Output:
Number: 1099511627775
kprintf("Number: %s\n", IntToString(-1234));
Output:
Number: -1234
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.