r0bin.xyz

Home About Blog Projects

C is Still Fun: Part II

Oct 26, 2024 9 min tech c coding library +3 fun printf ioccc

I was planning to get into the details of liblcu in this post, sharing some of the strange things I encountered while writing it (which, to be honest, wasn’t a whole lot). But liblcu is a pretty simple library, and I didn’t want to dedicate an entire post to it. So, instead, let’s talk about something I saw recently that really left me scratching my head.

If you’re not familiar with IOCCC, it’s a competition where participants submit obfuscated C source code that do a wide range of things, here’s an example from a winning entry.

If you don’t know what the heck is going on, I don’t blame you. Even after knowing how it works I still don’t know how to make sense of it. A winning entry indeed…

I found the readme to be very insightful.

In part one of this post, I talked about function pointers and used printf() to show some “undefined behavior”. After writing that post I was left wanting to look into printf because after a decade of writing C, I didn’t know how it worked. I got excited, poured myself a cup of coffee, cloned glibc, opened up VSCode, and dove through the source, to realize I was in way over my head… It was just too much for my caffeinated brain.

Instead, I decided to live in ignorance and become a printf programmer, even though it was more or less a black box to me. Maybe I would learn how it worked as I messed with it.

As a first project I wanted to write a guessing game. Guess the right number and you win, guess it wrong and you try again. Unlimited attempts.

I started by reading the man page for printf, and that readme from the IOCCC entry I shared before. First I wanted to give the following snippet a try:

printf("%1$.*2$d%3$hhn", 5, 10, &x);

Because this is the foundation on which the guessing game will be built, it might be worth our time to go over what it’s doing.

%1$.*2$d
    - %1$: tells printf to grab the first argument after the format string (which is 5 here)
    - .*2$: uses the second argument to set the precision, so we’ll print 5 but padded out to 10 digits (meaning we get 0000000005)
    - d: this is an integer specifier
%3$hhn
    - %3$: uses the third argument (&x) for this part
    - hhn: doesn’t print anything, instead it takes the number of characters printed so far and writes that value to the address we passed (&x) as a single char

Now moving on, that worked. But I didn’t like that it printed 0000000005. I want to print x = 10 instead, but since we’re working within the confines of a single printf call, I need to turn to ANSI escape sequences. Check out the Wikipedia page to know more. Luckily this isn’t my first rodeo with ANSI escape sequences, see here. So next I tried this:

printf("%1$.*2$d%3$hhn\r\033[Kx = %4$d\n", 5, 10, &x, x);

Aw crud, it’s printing x = 0, I must be doing something wrong. Or am I?

int i = 1;
while(i-- >= 0) printf("%1$.*2$d%3$hhn\r\033[Kx = %4$d\n", 5, 10, &x, x);

Ok, now we’re cooking! It’s printing:

x = 0
x = 10

By the way, the program at this point looks like this:

#include <stdio.h>

int main() {
    char x = 0;
    int i = 1;
    while(i-- >= 0) printf("%1$.*2$d%3$hhn\r\033[Kx = %4$d\n", 5, 10, &x, x);
    return 0;
}

Now we know how to store data somewhere in memory, and we know that in order for some changes to take place we might need to waste a run through the loop. But now there’s this useless print at the beginning, when we just want to see x = 10. In order to get rid of it we need to evaluate whether x is 0, and return a string that could clear the line. You could do it like this:

while(i-- >= 0) printf("%1$.*2$d%3$hhn\r\033[Kx = %4$d%5$s", 5, 10, &x, x, x?"\n":"\r\033[K");

Now we only see x = 10, nice! Let’s not forget we’re doing this in a single call to printf().

Next for the guessing game, I would like to generate a random number, so naturally I thought about rand() but the thought of having to include <stdlib.h> made my stomach churn. I mentioned ASLR on my last post. You might know where I’m going with this. Instead of using rand, I could just grab standard library function addresses, and they should be different every time the program runs. If I convert that address to an integer and use modulo, I get my random number.

while(i-- >= 0) printf("%1$.*2$d%3$hhn\r\033[Kx = %4$d%5$s", 5, ((((long)&printf>>16)&0xFF))%10, &x, x, x?"\n":"\r\033[K");

I’ve replaced the hardcoded 10 with a “random number generator”. I get the address of printf and grab a byte from the middle modulo 10, to generate a random number between 1 and 10. This let’s us get away with not using rand, with the caveat that if ASLR is turned off, or not supported, the game will be fun only the first time you play it.

Now I have two problems:

  1. I’m printing the secret number on the screen
  2. It’s not a real game until it can take guesses

Let’s tackle the first problem, and make a few changes along the way:

  1. No more variables, create a giant byte array instead
  2. For the while loop, use the first byte in the array as the stop signal
#include <stdio.h>

char b[16] = {0,0};

int main() {
    while(!*b) printf("%1$.*2$d%3$hhn\r\033[Kx = %4$d%5$s", *b, ((((long)&printf>>16)&0xFF))%10, &b[1], b[1], b[1]?"\n":"\r\033[K", *b=b[1]?1:0);
    return 0;
}

Now let’ tackle the second problem: taking guesses from players.

#include <stdio.h>

#define RAND(x) (((((long)&printf>>16)&0xFF))%x)

char b[16] = {1, 10, 1};

int main() {
    while(b[0]) printf("%1$.*2$d%3$hhn\r\033[KGuess 1 - %4$d:%7$s\n", b[0], RAND(b[1]), &b[2], b[1], b[9]='d', b[8]=(scanf(&b[8], &b[3])==EOF?:'%'), ((b[2]==b[3])?"\r\033[KYou won!\0":" "), b[0]=((b[2]==b[3])?0:1));
    return 0;
}

Let’s look at how the game reads guesses. printf() will look to evaluate the functions within it’s argument list, meaning scanf() will be called before anything is printed on the screen. Obviously this is not what we want, we would like to prompt the player first. What we can do though, is feed scanf a format string that’s half complete, say a region in memory that looks like this [...|0|'d'|0|...], where that first 0 is position 64 in the b array. The function in that case returns an error, which is not EOF, meaning that region in memory will now look like this [...|'%'|'d'|0|...], since we set b[64] to '%' on anything other than EOF. So the first scanf call won’t do anything, allowing us to prompt the user, and the second one onwards will read an integer from stdin into b[3]. Pretty neat!

After that we simply check to see if the random number we generated and stored in b[2] is equal to the number stored in b[3] before triggering a winning message and setting the exit flag.

Now let’s touch it up a bit.

#include <stdio.h>

#define N 10

#define EXIT        b[0]
#define MAX         b[1]
#define SECRET_NUM  b[2]
#define GUESSED_NUM b[3]
#define SCANF_FMT   b[8]
#define RAND        (((((long)&printf>>16)&0xFF))%MAX)

char b[16] = {0, N, 1};

char *fmt = "%1$.*2$d%3$hhn\r\033[KGuess 0 - %4$d:%7$s\n";
#define arg         0, RAND, &SECRET_NUM, MAX, \
                    *(&SCANF_FMT+1)='d', SCANF_FMT=(scanf(&SCANF_FMT, &GUESSED_NUM)==EOF?:'%'), \
                    ((SECRET_NUM==GUESSED_NUM)?"\r\033[KYou won!\0":" "), \
                    EXIT=(SECRET_NUM==GUESSED_NUM)

int main() {
    while(!EXIT) printf(fmt, arg);
    return 0;
}

That’s our guessing game in one printf(). For the next few months I’m going to be working on something bigger using one printf() as well. I hope you had fun with these two posts, not as related to each other as I would have liked, but I had a lot of fun writing them.