Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternatives to register_printf_specifier ( to print numbers in binary format using printf() )

Tags:

c

printf

glibc

I understand that register_printf_specifier is now deprecated.

I can no longer run code using register_printf_specifier using the C99 compiler at www.onlinegdb.com.

e.g. I really wanted to run the following code that adds the %B format specifier to printf() in order to print out integers in binary (from Is there a printf converter to print in binary format?):

/*
 * File:   main.c
 * Author: Techplex.Engineer
 *
 * Created on February 14, 2012, 9:16 PM
 */

#include <stdio.h>
#include <stdlib.h>
#include <printf.h>
#include <math.h>
#include <string.h>
#include <stdarg.h>

static int printf_arginfo_M(const struct printf_info *info, size_t n, int *argtypes)
{
    /* "%M" always takes one argument, a pointer to uint8_t[6]. */
    if (n > 0) {
        argtypes[0] = PA_POINTER;
   }
    return 1;
}


static int printf_output_M(FILE *stream, const struct printf_info *info, const void *const *args)
{
    int value = 0;
    int len;

    value = *(int *) (args[0]);

    // Beginning of my code ------------------------------------------------------------
    //char buffer [50] = "";  // Is this bad?
    char* buffer = (char*) malloc(sizeof(char) * 50);
    // char buffer2 [50] = "";  // Is this bad?
    char* buffer2 = (char*) malloc(sizeof(char) * 50);
    int bits = info->width;
    if (bits <= 0)
        bits = 8;  // Default to 8 bits

    int mask = pow(2, bits - 1);
    while (mask > 0) {
        sprintf(buffer, "%s", ((value & mask) > 0 ? "1" : "0"));
        strcat(buffer2, buffer);
        mask >>= 1;
    }
    strcat(buffer2, "\n");
    // End of my code --------------------------------------------------------------
    len = fprintf(stream, "%s", buffer2);
    free (buffer);
    free (buffer2);
    return len;
}

int main(int argc, char** argv)
{
    register_printf_specifier('B', printf_output_M, printf_arginfo_M);

    printf("%4B\n", 65);

    return EXIT_SUCCESS;
}

When I do, I get:

main.c:65:53: warning: passing argument 3 of ‘register_printf_specifier’ from incompatible pointer type [-Wincompatible-pointer-types]
     register_printf_specifier('B', printf_output_M, printf_arginfo_M);
                                                     ^~~~~~~~~~~~~~~~
In file included from main.c:18:0:
/usr/include/printf.h:96:12: note: expected ‘int (*)(const struct printf_info *, size_t,  int *, int *) {aka int (*)(const struct printf_info *, long unsigned int,  int *, int *)}’ but argument is of type ‘int (*)(const struct printf_info *, size_t,  int *) {aka int (*)(const struct printf_info *, long unsigned int,  int *)}’
 extern int register_printf_specifier (int __spec, printf_function __func,
            ^~~~~~~~~~~~~~~~~~~~~~~~~
main.c:67:15: warning: unknown conversion type character ‘B’ in format [-Wformat=]
     printf("%4B\n", 65);
               ^
main.c:67:12: warning: too many arguments for format [-Wformat-extra-args]
     printf("%4B\n", 65);
            ^~~~~~~
000

Since register_printf_specifier is now deprecated, what are programmers supposed to use instead? Create one's own variadic printf() like function?

P.S. Below is the updated code that corrects the errors pointed out to me. Make sure you use the right format specifier for the type of integer that you want to display in binary. (e.g. %hhB for chars and %hB for shorts). You can pad with spaces or zeros (e.g. %018hB will add 2 leading zeros to the binary since the size of shorts is 16 bits on the computer I am using). Please note: Make sure you use the right format specifier! If you do not, the binary output will likely be wrong especially for negative integers or unsigned integers.

/*
 * File:        main.c
 * Author:      Techplex.Engineer
 * Modified by: Robert Kennedy
 *
 * Created on February 14, 2012, 9:16 PM
 * Modified on August 28, 2021, 9:06 AM
 */

//
// The following #pragma's are the only way to supress the compiler warnings
// because there is no way of letting -Wformat know about the 
// custom %B format specifier.
//
#pragma GCC diagnostic ignored "-Wformat="  
#pragma GCC diagnostic ignored "-Wformat-extra-args"

#include <stdio.h>      // Needed for fprintf(); sprintf() and printf();
#include <stdlib.h>     // Needed for exit(); malloc(); and free(); and 
                        // EXIT_SUCCESS macro constant.
#include <printf.h>     // Needed for register_printf_specifier(); and related
                        // data structures (like "const struct print_info") and 
                        // related macro constants (e.g. PA_POINTER)
#include <math.h>       // Needed for pow(); and powl();
#include <string.h>     // Needed for strcat; strncat; memset();
#include <limits.h>     // Needed for min and max values for the various integer
                        // data types.
#include <inttypes.h>   // Needed for int64_t data types and the min and max 
                        // values.

static int printf_arginfo_B(const struct printf_info *info, size_t n, int *argtypes, int* size)
{
    if  (info->is_long_double)
        *size = sizeof(long long);  /* Optional to specify *size here */
    else if  (info->is_long)
        *size = sizeof(long);       /* Optional to specify *size here */
    else
        *size = sizeof(int);        /* Optional to specify *size here */
        
    if (n > 0)                      /* means there are arguments! */
    {
        argtypes[0] = PA_POINTER;   /* Specifies a void* pointer type */
   }
    return 1;
}

static int printf_output_B(FILE *stream, const struct printf_info *info, const void *const *args)
{
    const int sizeOfByte = CHAR_BIT;
    const int charSizeInBits = sizeof(char) * sizeOfByte;
    const int shortSizeInBits = sizeof(short) * sizeOfByte;
    const int intSizeInBits = sizeof(int) * sizeOfByte;
    const int longSizeInBits = sizeof(long) * sizeOfByte;
    const int longlongSizeInBits = sizeof(long long) * sizeOfByte;
    
    unsigned int intValue = 0;
    unsigned long longValue =  0l;
    unsigned long long longlongValue = 0ll;
    
    int len;                    // Length of the string (containing the binary
                                // number) that was printed.
                                // On error, a negative number will be returned.
                                
    int i;                      // A simple counter variable.
    
    int displayBits;            // Number of bits to be displayed
                                // If greater than calcBits, leading zeros
                                // will be displayed.
                                
    int calcBits;               // The minimum number of bits needed for the 
                                // decimcal to binary conversion.
    
    displayBits = info->width;
    wchar_t padWithZero = info->pad;
    char padChar = ' ';
    
    if (info->is_long_double) 
    {
        calcBits = longlongSizeInBits;
        if (displayBits < longlongSizeInBits)
        {
            displayBits = longlongSizeInBits;
        }
    }
    
    if (info->is_long) 
    {
        calcBits = longSizeInBits;
        if (displayBits < longSizeInBits)
        {
            displayBits = longSizeInBits;
        }
    }
    
    if ( !(info->is_long) && !(info->is_long_double) && !(info->is_short) && !(info->is_char) )
    {
        calcBits = intSizeInBits;
        if (displayBits < intSizeInBits)
        {
            displayBits = intSizeInBits;
        }
    }
    
    if (info->is_short)
    {
        calcBits = shortSizeInBits;
        if (displayBits < shortSizeInBits)
        {
            displayBits = shortSizeInBits;
        }
    }
    
    if (info->is_char)
    {
        calcBits = charSizeInBits;
        if (displayBits < charSizeInBits)
        {
            displayBits = charSizeInBits;
        }
    }
    
    // printf("\ndisplayBits = %d and calcBits = %d\n", displayBits, calcBits);
    
    char* buffer = (char*) malloc(sizeof(char) * (displayBits+1));
    char* buffer2 = (char*) malloc(sizeof(char) * (displayBits+1));
    
    if ( info->is_long_double )
    {
        longlongValue= * ((unsigned long long *) (args[0]));
        unsigned long long mask = powl(2, calcBits - 1);
        while (mask > 0) 
        {
            sprintf(buffer, "%s", ((longlongValue & mask) > 0 ? "1" : "0"));
            // strcat(buffer2, buffer);
            strncat(buffer2, buffer, displayBits-( (int)strlen(buffer2)) );
            mask >>= 1;
        }
    }
    else if ( info->is_long )
    {
        longValue= * ((unsigned long *) (args[0]));
        unsigned long mask = powl(2, calcBits - 1);
        while (mask > 0) 
        {
            sprintf(buffer, "%s", ((longValue & mask) > 0 ? "1" : "0"));
            // strcat(buffer2, buffer);
            strncat(buffer2, buffer, displayBits-( (int)strlen(buffer2)) );
            mask >>= 1;
        }
    }
    else
    {
        intValue = * ((unsigned int *) (args[0]));
        unsigned long mask = pow(2, calcBits - 1);
        while (mask > 0) {
        sprintf(buffer, "%s", ((intValue & mask) > 0 ? "1" : "0"));
        // strcat(buffer2, buffer);
        strncat(buffer2, buffer, displayBits-( (int)strlen(buffer2)) );
        mask >>= 1;
        }
    }
    
    strcat(buffer2, "\0");
    
    if (displayBits > calcBits)
    {
        if ('0' == padWithZero) 
            padChar = '0';
        else
            padChar = ' ';
        memset(buffer, '\0', displayBits);
        memset(buffer, padChar, (displayBits-calcBits));
        strncat(buffer, buffer2, displayBits-( (int)strlen(buffer)) );
        len = fprintf(stream, "%s", buffer);
    }
    else
    {
        len = fprintf(stream, "%s", buffer2);
    }
    
    free (buffer);
    free (buffer2);
    
    return len;
}

int main(int argc, char** argv)
{
    const int sizeOfByte = 8;
    
    register_printf_specifier('B', printf_output_B, printf_arginfo_B);

    printf("Sizeof(char) is: %ld bits\n", sizeof(char) * sizeOfByte);
    printf("CHAR_MAX %hhd in binary is: %hhB\n", CHAR_MAX, CHAR_MAX);
    printf("CHAR_MIN %hhd in binary is: %hhB\n",  CHAR_MIN, CHAR_MIN);
    printf("UCHAR_MAX %hhu (unsigned) in binary is: %hhB\n", UCHAR_MAX, UCHAR_MAX);
    printf("%hhd in binary is: %hhB\n",  -5, -5);
    printf(" %hhd in binary is: %hhB\n\n",  0, 0);
    
    printf("Sizeof(short) is: %ld bits\n", sizeof(short) * sizeOfByte);
    printf("SHRT_MAX  %hd in binary is: %hB\n", SHRT_MAX, SHRT_MAX);
    printf("SHRT_MIN %hd in binary is: %hB\n", SHRT_MIN, SHRT_MIN);
    printf("USHRT_MAX %hu (unsigned) in binary is: %hB\n", USHRT_MAX, USHRT_MAX);
    printf("USHRT_MAX %hu (unsigned) in binary with 2 leading  zeros is: %018hB\n", USHRT_MAX, USHRT_MAX);
    printf("USHRT_MAX %hu (unsigned) in binary with 2 leading spaces is: %18hB\n\n", USHRT_MAX, USHRT_MAX);
    
    printf("Sizeof(int) is: %ld bits\n", sizeof(int) * sizeOfByte);
    printf("INT_MAX  %d in binary is: %B\n",  INT_MAX, INT_MAX);
    printf("INT_MIN %d in binary is: %B\n",  INT_MIN, INT_MIN);
    printf("UINT_MAX %u (unsigned) in binary is: %B\n",  UINT_MAX, UINT_MAX);
    printf("UINT_MAX %u (unsigned) in binary with 4 leading zeros is: %036B\n\n",  UINT_MAX, UINT_MAX);
    
    printf("Sizeof(long) is: %ld bits\n", sizeof(long) * sizeOfByte);
    printf("LONG_MAX  %ld in binary is: %lB\n", LONG_MAX, LONG_MAX);
    printf("LONG_MIN %ld in binary is: %lB\n", LONG_MIN, LONG_MIN);
    printf("ULONG_MAX %lu (unsigned) in binary is: %lB\n\n", ULONG_MAX, ULONG_MAX);
    
    printf("Sizeof(long long) is: %ld bits\n", sizeof(long long) * sizeOfByte);
    printf("LLONG_MAX  %lld in binary is: %llB\n", LLONG_MAX, LLONG_MAX);
    printf("LLONG_MIN %ld in binary is: %lB\n", LLONG_MIN, LLONG_MIN);
    printf("ULLONG_MAX %llu (unsigned) in binary is: %llB\n\n", ULLONG_MAX, ULLONG_MAX);
    
    printf("Sizeof(int64_t) is: %ld bits\n", sizeof(int64_t) * sizeOfByte);
    printf("INT_64_MAX  %lld in binary is: %LB\n", INT64_MAX, INT64_MAX);
    printf("INT_64_MIN %lld in binary is: %LB\n", INT64_MIN, INT64_MIN);
    printf("UINT64_MAX %llu in binary is: %LB\n", UINT64_MAX, UINT64_MAX);
    
    return EXIT_SUCCESS;
}

Below is the output:

Sizeof(char) is: 8 bits
CHAR_MAX 127 in binary is: 01111111
CHAR_MIN -128 in binary is: 10000000
UCHAR_MAX 255 (unsigned) in binary is: 11111111
-5 in binary is: 11111011
 0 in binary is: 00000000

Sizeof(short) is: 16 bits
SHRT_MAX  32767 in binary is: 0111111111111111
SHRT_MIN -32768 in binary is: 1000000000000000
USHRT_MAX 65535 (unsigned) in binary is: 1111111111111111
USHRT_MAX 65535 (unsigned) in binary with 2 leading  zeros is: 001111111111111111
USHRT_MAX 65535 (unsigned) in binary with 2 leading spaces is:   1111111111111111

Sizeof(int) is: 32 bits
INT_MAX  2147483647 in binary is: 01111111111111111111111111111111
INT_MIN -2147483648 in binary is: 10000000000000000000000000000000
UINT_MAX 4294967295 (unsigned) in binary is: 11111111111111111111111111111111
UINT_MAX 4294967295 (unsigned) in binary with 4 leading zeros is: 000011111111111111111111111111111111

Sizeof(long) is: 64 bits
LONG_MAX  9223372036854775807 in binary is: 0111111111111111111111111111111111111111111111111111111111111111
LONG_MIN -9223372036854775808 in binary is: 1000000000000000000000000000000000000000000000000000000000000000
ULONG_MAX 18446744073709551615 (unsigned) in binary is: 1111111111111111111111111111111111111111111111111111111111111111

Sizeof(long long) is: 64 bits
LLONG_MAX  9223372036854775807 in binary is: 0111111111111111111111111111111111111111111111111111111111111111
LLONG_MIN -9223372036854775808 in binary is: 1000000000000000000000000000000000000000000000000000000000000000
ULLONG_MAX 18446744073709551615 (unsigned) in binary is: 1111111111111111111111111111111111111111111111111111111111111111

Sizeof(int64_t) is: 64 bits
INT_64_MAX  9223372036854775807 in binary is: 0111111111111111111111111111111111111111111111111111111111111111
INT_64_MIN -9223372036854775808 in binary is: 1000000000000000000000000000000000000000000000000000000000000000
UINT64_MAX 18446744073709551615 in binary is: 1111111111111111111111111111111111111111111111111111111111111111
like image 938
RobK Avatar asked Mar 02 '23 11:03

RobK


1 Answers

register_printf_specifier is not deprecated - register_printf_function is deprecated. Your code does compile and run - you can see that it prints binary at the end - you just have some compiler warnings. If you need proof that register_printf_specifier is valid, see in printf.h:

typedef int printf_arginfo_size_function (const struct printf_info *__info,
                                          size_t __n, int *__argtypes,
                                          int *__size);
/* Old version of 'printf_arginfo_function' without a SIZE parameter.  */
typedef int printf_arginfo_function (const struct printf_info *__info,
                                     size_t __n, int *__argtypes);

...

/* Register FUNC to be called to format SPEC specifiers; ARGINFO must be
   specified to determine how many arguments a SPEC conversion requires and
   what their types are.  */
extern int register_printf_specifier (int __spec, printf_function __func,
                                      printf_arginfo_size_function __arginfo)
  __THROW;
/* Obsolete interface similar to register_printf_specifier.  It can only
   handle basic data types because the ARGINFO callback does not return
   information on the size of the user-defined type.  */
extern int register_printf_function (int __spec, printf_function __func,
                                     printf_arginfo_function __arginfo)
  __THROW __attribute_deprecated__;

i.e. in modern code, instead of using register_printf_function you should use register_printf_specifier. You'll notice that their signatures are very similar, with the only difference being that the last parameter is printf_arginfo_size_function as opposed to printf_arginfo_function.

And this is your problem - you are passing a parameter of type printf_arginfo_function to a function that expects a parameter of type printf_arginfo_size_function. You need to add a int* size parameter to printf_arginfo_M and fill it with the size of your parameter - i.e. *size = sizeof(int).

As an aside, you could have understood this from the compiler warning:

/usr/include/printf.h:96:12: note: expected ‘int (*)(const struct printf_info *, size_t, int *, int ) {aka int ()(const struct printf_info *, long unsigned int, int *, int )}’ but argument is of type ‘int ()(const struct printf_info *, size_t, int ) {aka int ()(const struct printf_info *, long unsigned int, int *)}’


As for your modifier function, consider your code:

int bits = info->width;
if (bits <= 0)
    bits = 8;  // Default to 8 bits

int mask = pow(2, bits - 1);

Consider that you're missing quite a few bits here - for instance for 65 you need 7 bits, and you're only printing the bottom 4 when you print "%4B".

Also, if you want to print 64-bit numbers in binary, then in the same way that you print 64-bit integers using %lld - you should print your numbers using %llB. Then, inside printf_output_m you can write code like this:

if (info->is_long_double) {
    long_value = *(uint64_t*)(args[0]);
    bits = 64;
}

notice that this requires a general redesign of your function - you must change maxBits to 64 (because you want to support printing up to 64 bits), etc.


As to the main.c:67:15: warning: unknown conversion type character ‘B’ in format [-Wformat=] warnings - these aren't avoidable without manually suppressing the -Wformat flag for your lines of code. As of now, there is no way of letting -Wformat know about your custom specifier.


Unrelated side note - Google's latest CTF had a very cool challenge involving reverse engineering a virtual machine written using register_printf_function (yes, the deprecated one) - you can see the source code here.

like image 193
Daniel Kleinstein Avatar answered Mar 04 '23 03:03

Daniel Kleinstein